diff --git a/docs/swagger.json b/docs/swagger.json index 262b0cfc..b3b7a5c5 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -90,6 +90,19 @@ } } }, + { + "name": "orderBy", + "in": "query", + "description": "The field from which you want to order the result. (Since v1.3.0)", + "enum": [ + "originalUrl", + "shortCode", + "dateCreated", + "visits" + ], + "required": false, + "type": "string" + }, { "$ref": "#/parameters/Authorization" } diff --git a/module/CLI/lang/es.mo b/module/CLI/lang/es.mo index 06f0a3db..3365694a 100644 Binary files a/module/CLI/lang/es.mo and b/module/CLI/lang/es.mo differ diff --git a/module/CLI/lang/es.po b/module/CLI/lang/es.po index f2c806c7..dab5bdb7 100644 --- a/module/CLI/lang/es.po +++ b/module/CLI/lang/es.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: Shlink 1.0\n" -"POT-Creation-Date: 2016-10-22 13:14+0200\n" -"PO-Revision-Date: 2016-10-22 13:15+0200\n" +"POT-Creation-Date: 2016-10-22 23:12+0200\n" +"PO-Revision-Date: 2016-10-22 23:13+0200\n" "Last-Translator: Alejandro Celaya \n" "Language-Team: \n" "Language: es_ES\n" @@ -172,6 +172,12 @@ msgstr "" msgid "A comma-separated list of tags to filter results" msgstr "Una lista de etiquetas separadas por coma para filtrar el resultado" +msgid "" +"The field from which we want to order by. Pass ASC or DESC separated by a " +"comma" +msgstr "" +"El campo por el cual queremos ordernar. Pasa ASC o DESC separado por una coma" + msgid "Whether to display the tags or not" msgstr "Si se desea mostrar las etiquetas o no" diff --git a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php index bfe2ae6f..a8594db7 100644 --- a/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php +++ b/module/CLI/src/Command/Shortcode/ListShortcodesCommand.php @@ -70,6 +70,14 @@ class ListShortcodesCommand extends Command InputOption::VALUE_OPTIONAL, $this->translator->translate('A comma-separated list of tags to filter results') ) + ->addOption( + 'orderBy', + 'o', + InputOption::VALUE_OPTIONAL, + $this->translator->translate( + 'The field from which we want to order by. Pass ASC or DESC separated by a comma' + ) + ) ->addOption( 'showTags', null, @@ -85,12 +93,13 @@ class ListShortcodesCommand extends Command $tags = $input->getOption('tags'); $tags = ! empty($tags) ? explode(',', $tags) : []; $showTags = $input->getOption('showTags'); + $orderBy = $input->getOption('orderBy'); /** @var QuestionHelper $helper */ $helper = $this->getHelper('question'); do { - $result = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags); + $result = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags, $this->processOrderBy($input)); $page++; $table = new Table($output); @@ -136,4 +145,15 @@ class ListShortcodesCommand extends Command } } while ($continue); } + + protected function processOrderBy(InputInterface $input) + { + $orderBy = $input->getOption('orderBy'); + if (empty($orderBy)) { + return null; + } + + $orderBy = explode(',', $orderBy); + return count($orderBy) === 1 ? $orderBy[0] : [$orderBy[0] => $orderBy[1]]; + } } diff --git a/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php b/module/CLI/test/Command/Shortcode/ListShortcodesCommandTest.php index 306cb87b..03ac0e0e 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, [], 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, [], null)->willReturn(new Paginator(new ArrayAdapter())) + ->shouldBeCalledTimes(1); $this->commandTester->execute([ 'command' => 'shortcode:list', @@ -118,8 +118,8 @@ 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, [], null)->willReturn(new Paginator(new ArrayAdapter())) + ->shouldBeCalledTimes(1); $this->commandTester->execute([ 'command' => 'shortcode:list', diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index a287fe0b..256cb985 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -20,21 +20,46 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI $qb = $this->createListQueryBuilder($searchTerm, $tags); $qb->select('s'); + // Set limit and offset if (isset($limit)) { $qb->setMaxResults($limit); } if (isset($offset)) { $qb->setFirstResult($offset); } + + // In case the ordering has been specified, the query could be more complex. Process it if (isset($orderBy)) { - if (is_string($orderBy)) { - $qb->orderBy($orderBy); - } elseif (is_array($orderBy)) { - $key = key($orderBy); - $qb->orderBy($key, $orderBy[$key]); - } - } else { - $qb->orderBy('s.dateCreated'); + return $this->processOrderByForList($qb, $orderBy); + } + + // With no order by, order by date and just return the list of ShortUrls + $qb->orderBy('s.dateCreated'); + return $qb->getQuery()->getResult(); + } + + protected function processOrderByForList(QueryBuilder $qb, $orderBy) + { + $fieldName = is_array($orderBy) ? key($orderBy) : $orderBy; + $order = is_array($orderBy) ? $orderBy[$fieldName] : 'ASC'; + + if (in_array($fieldName, [ + 'visits', + 'visitsCount', + 'visitCount', + ])) { + $qb->addSelect('COUNT(v) AS totalVisits') + ->leftJoin('s.visits', 'v') + ->groupBy('s') + ->orderBy('totalVisits', $order); + + return array_column($qb->getQuery()->getResult(), 0); + } elseif (in_array($fieldName, [ + 'originalUrl', + 'shortCode', + 'dateCreated', + ])) { + $qb->orderBy('s.' . $fieldName, $order); } return $qb->getQuery()->getResult(); diff --git a/module/Core/src/Service/ShortUrlService.php b/module/Core/src/Service/ShortUrlService.php index faf778b1..59c76565 100644 --- a/module/Core/src/Service/ShortUrlService.php +++ b/module/Core/src/Service/ShortUrlService.php @@ -34,13 +34,14 @@ class ShortUrlService implements ShortUrlServiceInterface * @param int $page * @param string $searchQuery * @param array $tags + * @param null $orderBy * @return ShortUrl[]|Paginator */ - public function listShortUrls($page = 1, $searchQuery = null, array $tags = []) + public function listShortUrls($page = 1, $searchQuery = null, array $tags = [], $orderBy = null) { /** @var ShortUrlRepository $repo */ $repo = $this->em->getRepository(ShortUrl::class); - $paginator = new Paginator(new PaginableRepositoryAdapter($repo, $searchQuery, $tags)); + $paginator = new Paginator(new PaginableRepositoryAdapter($repo, $searchQuery, $tags, $orderBy)); $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 cdf9a45f..bc9b8daf 100644 --- a/module/Core/src/Service/ShortUrlServiceInterface.php +++ b/module/Core/src/Service/ShortUrlServiceInterface.php @@ -11,9 +11,10 @@ interface ShortUrlServiceInterface * @param int $page * @param string $searchQuery * @param array $tags + * @param null $orderBy * @return ShortUrl[]|Paginator */ - public function listShortUrls($page = 1, $searchQuery = null, array $tags = []); + public function listShortUrls($page = 1, $searchQuery = null, array $tags = [], $orderBy = null); /** * @param string $shortCode diff --git a/module/Rest/src/Action/ListShortcodesAction.php b/module/Rest/src/Action/ListShortcodesAction.php index dd08c071..aa987c2f 100644 --- a/module/Rest/src/Action/ListShortcodesAction.php +++ b/module/Rest/src/Action/ListShortcodesAction.php @@ -75,6 +75,7 @@ class ListShortcodesAction extends AbstractRestAction isset($query['page']) ? $query['page'] : 1, isset($query['searchTerm']) ? $query['searchTerm'] : null, isset($query['tags']) ? $query['tags'] : [], + isset($query['orderBy']) ? $query['orderBy'] : null, ]; } } diff --git a/module/Rest/test/Action/ListShortcodesActionTest.php b/module/Rest/test/Action/ListShortcodesActionTest.php index df15bb0c..d17740be 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, [], 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, [], null)->willThrow(\Exception::class) + ->shouldBeCalledTimes(1); $response = $this->action->__invoke( ServerRequestFactory::fromGlobals()->withQueryParams([