From 8b9caf02d24640874294b61d22e69b7d6dfbed88 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2016 12:57:24 +0200 Subject: [PATCH 1/5] Added tags param to paginable repository adapter --- .../Adapter/PaginableRepositoryAdapter.php | 25 +++++++++++++++---- .../PaginableRepositoryInterface.php | 6 +++-- .../PaginableRepositoryAdapterTest.php | 6 ++--- .../src/Repository/ShortUrlRepository.php | 11 +++++--- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/module/Common/src/Paginator/Adapter/PaginableRepositoryAdapter.php b/module/Common/src/Paginator/Adapter/PaginableRepositoryAdapter.php index 017f5e89..995c7263 100644 --- a/module/Common/src/Paginator/Adapter/PaginableRepositoryAdapter.php +++ b/module/Common/src/Paginator/Adapter/PaginableRepositoryAdapter.php @@ -20,12 +20,21 @@ class PaginableRepositoryAdapter implements AdapterInterface * @var null|array|string */ private $orderBy; + /** + * @var array + */ + private $tags; - public function __construct(PaginableRepositoryInterface $paginableRepository, $searchQuery = null, $orderBy = null) - { + public function __construct( + PaginableRepositoryInterface $paginableRepository, + $searchTerm = null, + array $tags = [], + $orderBy = null + ) { $this->paginableRepository = $paginableRepository; - $this->searchTerm = trim(strip_tags($searchQuery)); + $this->searchTerm = trim(strip_tags($searchTerm)); $this->orderBy = $orderBy; + $this->tags = $tags; } /** @@ -37,7 +46,13 @@ class PaginableRepositoryAdapter implements AdapterInterface */ public function getItems($offset, $itemCountPerPage) { - return $this->paginableRepository->findList($itemCountPerPage, $offset, $this->searchTerm, $this->orderBy); + return $this->paginableRepository->findList( + $itemCountPerPage, + $offset, + $this->searchTerm, + $this->tags, + $this->orderBy + ); } /** @@ -51,6 +66,6 @@ class PaginableRepositoryAdapter implements AdapterInterface */ public function count() { - return $this->paginableRepository->countList($this->searchTerm); + return $this->paginableRepository->countList($this->searchTerm, $this->tags); } } diff --git a/module/Common/src/Repository/PaginableRepositoryInterface.php b/module/Common/src/Repository/PaginableRepositoryInterface.php index b9a75aea..7481d186 100644 --- a/module/Common/src/Repository/PaginableRepositoryInterface.php +++ b/module/Common/src/Repository/PaginableRepositoryInterface.php @@ -9,16 +9,18 @@ interface PaginableRepositoryInterface * @param int|null $limit * @param int|null $offset * @param string|null $searchTerm + * @param array $tags * @param string|array|null $orderBy * @return array */ - public function findList($limit = null, $offset = null, $searchTerm = null, $orderBy = null); + public function findList($limit = null, $offset = null, $searchTerm = null, array $tags = [], $orderBy = null); /** * Counts the number of elements in a list using provided filtering data * * @param null $searchTerm + * @param array $tags * @return int */ - public function countList($searchTerm = null); + public function countList($searchTerm = null, array $tags = []); } diff --git a/module/Common/test/Paginator/PaginableRepositoryAdapterTest.php b/module/Common/test/Paginator/PaginableRepositoryAdapterTest.php index 1135682f..196cf0c9 100644 --- a/module/Common/test/Paginator/PaginableRepositoryAdapterTest.php +++ b/module/Common/test/Paginator/PaginableRepositoryAdapterTest.php @@ -20,7 +20,7 @@ class PaginableRepositoryAdapterTest extends TestCase public function setUp() { $this->repo = $this->prophesize(PaginableRepositoryInterface::class); - $this->adapter = new PaginableRepositoryAdapter($this->repo->reveal(), 'search', 'order'); + $this->adapter = new PaginableRepositoryAdapter($this->repo->reveal(), 'search', ['foo', 'bar'], 'order'); } /** @@ -28,7 +28,7 @@ class PaginableRepositoryAdapterTest extends TestCase */ public function getItemsFallbacksToFindList() { - $this->repo->findList(10, 5, 'search', 'order')->shouldBeCalledTimes(1); + $this->repo->findList(10, 5, 'search', ['foo', 'bar'], 'order')->shouldBeCalledTimes(1); $this->adapter->getItems(5, 10); } @@ -37,7 +37,7 @@ class PaginableRepositoryAdapterTest extends TestCase */ public function countFallbacksToCountList() { - $this->repo->countList('search')->shouldBeCalledTimes(1); + $this->repo->countList('search', ['foo', 'bar'])->shouldBeCalledTimes(1); $this->adapter->count(); } } diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index 9becb216..78962ccd 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -11,10 +11,11 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI * @param int|null $limit * @param int|null $offset * @param string|null $searchTerm + * @param array $tags * @param string|array|null $orderBy - * @return ShortUrl[] + * @return \Shlinkio\Shlink\Core\Entity\ShortUrl[] */ - public function findList($limit = null, $offset = null, $searchTerm = null, $orderBy = null) + public function findList($limit = null, $offset = null, $searchTerm = null, array $tags = [], $orderBy = null) { $qb = $this->createListQueryBuilder($searchTerm); $qb->select('s'); @@ -43,9 +44,10 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI * Counts the number of elements in a list using provided filtering data * * @param null|string $searchTerm + * @param array $tags * @return int */ - public function countList($searchTerm = null) + public function countList($searchTerm = null, array $tags = []) { $qb = $this->createListQueryBuilder($searchTerm); $qb->select('COUNT(s)'); @@ -55,9 +57,10 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI /** * @param null|string $searchTerm + * @param array $tags * @return QueryBuilder */ - protected function createListQueryBuilder($searchTerm = null) + protected function createListQueryBuilder($searchTerm = null, array $tags = []) { $qb = $this->getEntityManager()->createQueryBuilder(); $qb->from(ShortUrl::class, 's'); From 52bb14bd6654935bc40dad6f94d9c011a728fd31 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2016 13:04:17 +0200 Subject: [PATCH 2/5] Implemented filtering by tags in ListShortcodesAction --- module/Core/src/Repository/ShortUrlRepository.php | 13 ++++++++++--- module/Core/src/Service/ShortUrlService.php | 5 +++-- .../Core/src/Service/ShortUrlServiceInterface.php | 3 ++- module/Rest/src/Action/ListShortcodesAction.php | 1 + 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index 78962ccd..a287fe0b 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -17,7 +17,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI */ public function findList($limit = null, $offset = null, $searchTerm = null, array $tags = [], $orderBy = null) { - $qb = $this->createListQueryBuilder($searchTerm); + $qb = $this->createListQueryBuilder($searchTerm, $tags); $qb->select('s'); if (isset($limit)) { @@ -49,7 +49,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI */ public function countList($searchTerm = null, array $tags = []) { - $qb = $this->createListQueryBuilder($searchTerm); + $qb = $this->createListQueryBuilder($searchTerm, $tags); $qb->select('COUNT(s)'); return (int) $qb->getQuery()->getSingleScalarResult(); @@ -64,6 +64,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI { $qb = $this->getEntityManager()->createQueryBuilder(); $qb->from(ShortUrl::class, 's'); + $qb->where('1=1'); // Apply search term to every searchable field if not empty if (! empty($searchTerm)) { @@ -73,11 +74,17 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI ]; // Unpack and apply search conditions - $qb->where($qb->expr()->orX(...$conditions)); + $qb->andWhere($qb->expr()->orX(...$conditions)); $searchTerm = '%' . $searchTerm . '%'; $qb->setParameter('searchPattern', $searchTerm); } + // Filter by tags if provided + if (! empty($tags)) { + $qb->join('s.tags', 't') + ->andWhere($qb->expr()->in('t.name', $tags)); + } + return $qb; } } diff --git a/module/Core/src/Service/ShortUrlService.php b/module/Core/src/Service/ShortUrlService.php index e3c2e451..faf778b1 100644 --- a/module/Core/src/Service/ShortUrlService.php +++ b/module/Core/src/Service/ShortUrlService.php @@ -33,13 +33,14 @@ class ShortUrlService implements ShortUrlServiceInterface /** * @param int $page * @param string $searchQuery + * @param array $tags * @return ShortUrl[]|Paginator */ - public function listShortUrls($page = 1, $searchQuery = null) + public function listShortUrls($page = 1, $searchQuery = null, array $tags = []) { /** @var ShortUrlRepository $repo */ $repo = $this->em->getRepository(ShortUrl::class); - $paginator = new Paginator(new PaginableRepositoryAdapter($repo, $searchQuery)); + $paginator = new Paginator(new PaginableRepositoryAdapter($repo, $searchQuery, $tags)); $paginator->setItemCountPerPage(PaginableRepositoryAdapter::ITEMS_PER_PAGE) ->setCurrentPageNumber($page); diff --git a/module/Core/src/Service/ShortUrlServiceInterface.php b/module/Core/src/Service/ShortUrlServiceInterface.php index 7fbc1b4e..cdf9a45f 100644 --- a/module/Core/src/Service/ShortUrlServiceInterface.php +++ b/module/Core/src/Service/ShortUrlServiceInterface.php @@ -10,9 +10,10 @@ interface ShortUrlServiceInterface /** * @param int $page * @param string $searchQuery + * @param array $tags * @return ShortUrl[]|Paginator */ - public function listShortUrls($page = 1, $searchQuery = null); + public function listShortUrls($page = 1, $searchQuery = null, array $tags = []); /** * @param string $shortCode diff --git a/module/Rest/src/Action/ListShortcodesAction.php b/module/Rest/src/Action/ListShortcodesAction.php index d6f6782d..dd08c071 100644 --- a/module/Rest/src/Action/ListShortcodesAction.php +++ b/module/Rest/src/Action/ListShortcodesAction.php @@ -74,6 +74,7 @@ class ListShortcodesAction extends AbstractRestAction return [ isset($query['page']) ? $query['page'] : 1, isset($query['searchTerm']) ? $query['searchTerm'] : null, + isset($query['tags']) ? $query['tags'] : [], ]; } } From 47a2c18c7ee19b4f7aff4e93371b5ff84f77f694 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2016 13:11:24 +0200 Subject: [PATCH 3/5] Added the ability to filter by tag in shotcodes:list command --- .../Command/Shortcode/ListShortcodesCommand.php | 12 ++++++++++-- .../Shortcode/ListShortcodesCommandTest.php | 14 +++++++------- .../Rest/test/Action/ListShortcodesActionTest.php | 8 ++++---- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php index b8a1310c..bfe2ae6f 100644 --- a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php +++ b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php @@ -67,6 +67,12 @@ class ListShortcodesCommand extends Command ->addOption( 'tags', 't', + InputOption::VALUE_OPTIONAL, + $this->translator->translate('A comma-separated list of tags to filter results') + ) + ->addOption( + 'showTags', + null, InputOption::VALUE_NONE, $this->translator->translate('Whether to display the tags or not') ); @@ -76,13 +82,15 @@ class ListShortcodesCommand extends Command { $page = intval($input->getOption('page')); $searchTerm = $input->getOption('searchTerm'); - $showTags = $input->getOption('tags'); + $tags = $input->getOption('tags'); + $tags = ! empty($tags) ? explode(',', $tags) : []; + $showTags = $input->getOption('showTags'); /** @var QuestionHelper $helper */ $helper = $this->getHelper('question'); do { - $result = $this->shortUrlService->listShortUrls($page, $searchTerm); + $result = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags); $page++; $table = new Table($output); diff --git a/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php b/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php index 8ab0d232..306cb87b 100644 --- a/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php +++ b/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php @@ -46,8 +46,8 @@ class ListShortcodesCommandTest extends TestCase public function noInputCallsListJustOnce() { $this->questionHelper->setInputStream($this->getInputStream('\n')); - $this->shortUrlService->listShortUrls(1, null)->willReturn(new Paginator(new ArrayAdapter())) - ->shouldBeCalledTimes(1); + $this->shortUrlService->listShortUrls(1, null, [])->willReturn(new Paginator(new ArrayAdapter())) + ->shouldBeCalledTimes(1); $this->commandTester->execute(['command' => 'shortcode:list']); } @@ -103,8 +103,8 @@ class ListShortcodesCommandTest extends TestCase { $page = 5; $this->questionHelper->setInputStream($this->getInputStream('\n')); - $this->shortUrlService->listShortUrls($page, null)->willReturn(new Paginator(new ArrayAdapter())) - ->shouldBeCalledTimes(1); + $this->shortUrlService->listShortUrls($page, null, [])->willReturn(new Paginator(new ArrayAdapter())) + ->shouldBeCalledTimes(1); $this->commandTester->execute([ 'command' => 'shortcode:list', @@ -118,12 +118,12 @@ class ListShortcodesCommandTest extends TestCase public function ifTagsFlagIsProvidedTagsColumnIsIncluded() { $this->questionHelper->setInputStream($this->getInputStream('\n')); - $this->shortUrlService->listShortUrls(1, null)->willReturn(new Paginator(new ArrayAdapter())) - ->shouldBeCalledTimes(1); + $this->shortUrlService->listShortUrls(1, null, [])->willReturn(new Paginator(new ArrayAdapter())) + ->shouldBeCalledTimes(1); $this->commandTester->execute([ 'command' => 'shortcode:list', - '--tags' => true, + '--showTags' => true, ]); $output = $this->commandTester->getDisplay(); $this->assertTrue(strpos($output, 'Tags') > 0); diff --git a/module/Rest/test/Action/ListShortcodesActionTest.php b/module/Rest/test/Action/ListShortcodesActionTest.php index 9ff2e54a..df15bb0c 100644 --- a/module/Rest/test/Action/ListShortcodesActionTest.php +++ b/module/Rest/test/Action/ListShortcodesActionTest.php @@ -34,8 +34,8 @@ class ListShortcodesActionTest extends TestCase public function properListReturnsSuccessResponse() { $page = 3; - $this->service->listShortUrls($page, null)->willReturn(new Paginator(new ArrayAdapter())) - ->shouldBeCalledTimes(1); + $this->service->listShortUrls($page, null, [])->willReturn(new Paginator(new ArrayAdapter())) + ->shouldBeCalledTimes(1); $response = $this->action->__invoke( ServerRequestFactory::fromGlobals()->withQueryParams([ @@ -52,8 +52,8 @@ class ListShortcodesActionTest extends TestCase public function anExceptionsReturnsErrorResponse() { $page = 3; - $this->service->listShortUrls($page, null)->willThrow(\Exception::class) - ->shouldBeCalledTimes(1); + $this->service->listShortUrls($page, null, [])->willThrow(\Exception::class) + ->shouldBeCalledTimes(1); $response = $this->action->__invoke( ServerRequestFactory::fromGlobals()->withQueryParams([ From 230f2d155b09d50d46d4aa13360297d7c3d10e81 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2016 13:13:50 +0200 Subject: [PATCH 4/5] Documented tags param in GET /short-codes endpoint --- docs/swagger.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/swagger.json b/docs/swagger.json index d731f858..fea8a020 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -74,10 +74,22 @@ { "name": "searchTerm", "in": "query", - "description": "A query used to filter results by searching for it on the longUrl and shortCode fields. (From Shlink 1.3.0)", + "description": "A query used to filter results by searching for it on the longUrl and shortCode fields. (Since v1.3.0)", "required": false, "type": "string" }, + { + "name": "tags", + "in": "query", + "description": "A list of tags used to filter the resultset. Only short URLs tagged with at least one of the provided tags will be returned. (Since v1.3.0)", + "required": false, + "type": "array", + "schema": { + "items": { + "type": "string" + } + } + }, { "$ref": "#/parameters/Authorization" } From c22bbecdc5f8eb1d19fc1cd27997f2ecd566040f Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 22 Oct 2016 13:15:35 +0200 Subject: [PATCH 5/5] Updated languages --- module/CLI/lang/es.mo | Bin 6214 -> 6541 bytes module/CLI/lang/es.po | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/module/CLI/lang/es.mo b/module/CLI/lang/es.mo index 876787157c7561472ec5d4d9ad930cbf7d2d0184..06f0a3db516154e7b2427015304e07c32a01489a 100644 GIT binary patch delta 1721 zcmZ|PZ)jC@9LMpme~zwnE~nNk=YGvqT1~fmotkZS=W<#|Db5H<@p$jqcJb_2 zk+)|>6oi+0(4zt$B=#g0Nc5~g@DCUfte!|96~lzWCqfpzKW9e>q96O+*YBLW^ZWh2 z-{1G#3q9`@>(^Qreqgj6^fmOQ^UV(6odtZ*_IPIZ<8$}`9>pGf4WGfY_!7?IFqRgX zJ&SLl=DU(Ri+X<1VzWV9i$i90JIY`gH_l)yPUAwnfJ^Z*uEZaZHS8v~;4ORz7c|W+ z)Pc*n?nlk{0=|uhaV`FW&!czG+y?gHYSy>Y4DRKN&rm_1#})V$K8#n9Kl_6Zhkv3r zwuXna&<6Bz8*appuo*9+Ha>$}@i*LrT}#Z^yN%#7*0)y}D3!-?J)T4U?0Y_V#(qL& z_SH~jKg>sAICGe3a_G4{&Rl+FVuYN*&RbGVqFuK7(9q&T+aEQ!Zxno zC(p{jS>&wjGM2FAKC?r(5BK6Z+=aKX19xx^Pv8-3$9M2W{1jDNw^5nd+e-emkk7&E zY)_(U;7vaCU?ac(d4B&qDnplW1KvVK+Rk~gXDgzrc@Q7P863gCQ5!wLs#@=3WUuz+ zO7gFTFL6WF`YkGjH&Ll-WA^R14wbSas53u~A)Z8~{#VonHgX<3YW-Nk{ivfojl2N% z9TL3#i2LzIok1^y0S-zNN2rNTp^ENZR4PBjo%l7XnmxjJ8M|-`zJYvYUtspy<@aY% z3%b>FnJu6)vkM*8Lk9C0bkf(+6)p8;kI*;K`P!bQtFjfDvY=wr?}ZxkXH7w+SxBu(9Z@SccecsT)nxAdjcRU@WRkoA* z39Bli3ZqF@B}(7vMix)($1Vu-nEXoA=xo_}Nb{7Vn!@ynhDZPZ0e5UFDSz`xtrCU4 a3u=UtxUxSn2}+NmBJR5}O_y7)H~j-XPwp82 delta 1392 zcmYk+T}abW7{~Evxv4pAzGN#)oo_iy-R5de)0$4DQjoB~2m>Jq%3i3bi-Hvr6cR`) z@G2rgg1ixxf*^vRt8Tgx0xPJCq`C?!h!K5%b4G{%`G3yNwsW5U^PKHg_*F3RzSw!! zXdUz|^pwMF2$!sEw}SC{AKCe#Zi=VpRnmz*g+R0A4|2vKiF( z=TRAaiGKFCEVmh#HF9IlqNt3-u^RVc3!X%k;vV+l0&c?;mSR0oD&?K1`9rAnt|FIB zaMOBISccCqQMATvk-Fs+3`*=GKP#-arxg*Bd7?Z=6Lxug9?gXHZYJh)VG{)M@>VTXB-r4&oD3p!w8M z8y-etuyd&O$5ChICMq-2sLZ@6A^+_RmY5(3<|S;^s20aCj7s%=Q~;lm%YJZ+VII+` zmi@?qwh1IQyNw6%A%^fLYF$6|(>krlv9aC+1Eq2hyYVvW^ghR7e1{Q?P*~OW6gNG| zb=3R=)P`?Ssr`z|%pde&9qDkO#wu-;u8nKxP4onxvo?AoT?;5JY8@-251fX9%xe;r zZmM5%W?Yw1=qJX*{%iG&lwk$cLRXe``1JR}F-q7D26__JTTN+NZM6)P9i0g^9lq73 zEOB\n" "Language-Team: \n" "Language: es_ES\n" @@ -162,6 +162,16 @@ msgstr "Listar todas las URLs cortas" msgid "The first page to list (%s items per page)" msgstr "La primera página a listar (%s elementos por página)" +msgid "" +"A query used to filter results by searching for it on the longUrl and " +"shortCode fields" +msgstr "" +"Una consulta usada para filtrar el resultado buscándola en los campos " +"longUrl y shortCode" + +msgid "A comma-separated list of tags to filter results" +msgstr "Una lista de etiquetas separadas por coma para filtrar el resultado" + msgid "Whether to display the tags or not" msgstr "Si se desea mostrar las etiquetas o no"