Merge branch 'feature/58' into develop

This commit is contained in:
Alejandro Celaya 2016-10-22 13:16:13 +02:00
commit 850ce152cd
13 changed files with 96 additions and 36 deletions

View File

@ -74,10 +74,22 @@
{ {
"name": "searchTerm", "name": "searchTerm",
"in": "query", "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, "required": false,
"type": "string" "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" "$ref": "#/parameters/Authorization"
} }

Binary file not shown.

View File

@ -1,8 +1,8 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Shlink 1.0\n" "Project-Id-Version: Shlink 1.0\n"
"POT-Creation-Date: 2016-08-21 18:16+0200\n" "POT-Creation-Date: 2016-10-22 13:14+0200\n"
"PO-Revision-Date: 2016-08-21 18:16+0200\n" "PO-Revision-Date: 2016-10-22 13:15+0200\n"
"Last-Translator: Alejandro Celaya <alejandro@alejandrocelaya.com>\n" "Last-Translator: Alejandro Celaya <alejandro@alejandrocelaya.com>\n"
"Language-Team: \n" "Language-Team: \n"
"Language: es_ES\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)" msgid "The first page to list (%s items per page)"
msgstr "La primera página a listar (%s elementos por página)" 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" msgid "Whether to display the tags or not"
msgstr "Si se desea mostrar las etiquetas o no" msgstr "Si se desea mostrar las etiquetas o no"

View File

@ -67,6 +67,12 @@ class ListShortcodesCommand extends Command
->addOption( ->addOption(
'tags', 'tags',
't', 't',
InputOption::VALUE_OPTIONAL,
$this->translator->translate('A comma-separated list of tags to filter results')
)
->addOption(
'showTags',
null,
InputOption::VALUE_NONE, InputOption::VALUE_NONE,
$this->translator->translate('Whether to display the tags or not') $this->translator->translate('Whether to display the tags or not')
); );
@ -76,13 +82,15 @@ class ListShortcodesCommand extends Command
{ {
$page = intval($input->getOption('page')); $page = intval($input->getOption('page'));
$searchTerm = $input->getOption('searchTerm'); $searchTerm = $input->getOption('searchTerm');
$showTags = $input->getOption('tags'); $tags = $input->getOption('tags');
$tags = ! empty($tags) ? explode(',', $tags) : [];
$showTags = $input->getOption('showTags');
/** @var QuestionHelper $helper */ /** @var QuestionHelper $helper */
$helper = $this->getHelper('question'); $helper = $this->getHelper('question');
do { do {
$result = $this->shortUrlService->listShortUrls($page, $searchTerm); $result = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags);
$page++; $page++;
$table = new Table($output); $table = new Table($output);

View File

@ -46,7 +46,7 @@ class ListShortcodesCommandTest extends TestCase
public function noInputCallsListJustOnce() public function noInputCallsListJustOnce()
{ {
$this->questionHelper->setInputStream($this->getInputStream('\n')); $this->questionHelper->setInputStream($this->getInputStream('\n'));
$this->shortUrlService->listShortUrls(1, null)->willReturn(new Paginator(new ArrayAdapter())) $this->shortUrlService->listShortUrls(1, null, [])->willReturn(new Paginator(new ArrayAdapter()))
->shouldBeCalledTimes(1); ->shouldBeCalledTimes(1);
$this->commandTester->execute(['command' => 'shortcode:list']); $this->commandTester->execute(['command' => 'shortcode:list']);
@ -103,7 +103,7 @@ class ListShortcodesCommandTest extends TestCase
{ {
$page = 5; $page = 5;
$this->questionHelper->setInputStream($this->getInputStream('\n')); $this->questionHelper->setInputStream($this->getInputStream('\n'));
$this->shortUrlService->listShortUrls($page, null)->willReturn(new Paginator(new ArrayAdapter())) $this->shortUrlService->listShortUrls($page, null, [])->willReturn(new Paginator(new ArrayAdapter()))
->shouldBeCalledTimes(1); ->shouldBeCalledTimes(1);
$this->commandTester->execute([ $this->commandTester->execute([
@ -118,12 +118,12 @@ class ListShortcodesCommandTest extends TestCase
public function ifTagsFlagIsProvidedTagsColumnIsIncluded() public function ifTagsFlagIsProvidedTagsColumnIsIncluded()
{ {
$this->questionHelper->setInputStream($this->getInputStream('\n')); $this->questionHelper->setInputStream($this->getInputStream('\n'));
$this->shortUrlService->listShortUrls(1, null)->willReturn(new Paginator(new ArrayAdapter())) $this->shortUrlService->listShortUrls(1, null, [])->willReturn(new Paginator(new ArrayAdapter()))
->shouldBeCalledTimes(1); ->shouldBeCalledTimes(1);
$this->commandTester->execute([ $this->commandTester->execute([
'command' => 'shortcode:list', 'command' => 'shortcode:list',
'--tags' => true, '--showTags' => true,
]); ]);
$output = $this->commandTester->getDisplay(); $output = $this->commandTester->getDisplay();
$this->assertTrue(strpos($output, 'Tags') > 0); $this->assertTrue(strpos($output, 'Tags') > 0);

View File

@ -20,12 +20,21 @@ class PaginableRepositoryAdapter implements AdapterInterface
* @var null|array|string * @var null|array|string
*/ */
private $orderBy; 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->paginableRepository = $paginableRepository;
$this->searchTerm = trim(strip_tags($searchQuery)); $this->searchTerm = trim(strip_tags($searchTerm));
$this->orderBy = $orderBy; $this->orderBy = $orderBy;
$this->tags = $tags;
} }
/** /**
@ -37,7 +46,13 @@ class PaginableRepositoryAdapter implements AdapterInterface
*/ */
public function getItems($offset, $itemCountPerPage) 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() public function count()
{ {
return $this->paginableRepository->countList($this->searchTerm); return $this->paginableRepository->countList($this->searchTerm, $this->tags);
} }
} }

View File

@ -9,16 +9,18 @@ interface PaginableRepositoryInterface
* @param int|null $limit * @param int|null $limit
* @param int|null $offset * @param int|null $offset
* @param string|null $searchTerm * @param string|null $searchTerm
* @param array $tags
* @param string|array|null $orderBy * @param string|array|null $orderBy
* @return array * @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 * Counts the number of elements in a list using provided filtering data
* *
* @param null $searchTerm * @param null $searchTerm
* @param array $tags
* @return int * @return int
*/ */
public function countList($searchTerm = null); public function countList($searchTerm = null, array $tags = []);
} }

View File

@ -20,7 +20,7 @@ class PaginableRepositoryAdapterTest extends TestCase
public function setUp() public function setUp()
{ {
$this->repo = $this->prophesize(PaginableRepositoryInterface::class); $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() 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); $this->adapter->getItems(5, 10);
} }
@ -37,7 +37,7 @@ class PaginableRepositoryAdapterTest extends TestCase
*/ */
public function countFallbacksToCountList() public function countFallbacksToCountList()
{ {
$this->repo->countList('search')->shouldBeCalledTimes(1); $this->repo->countList('search', ['foo', 'bar'])->shouldBeCalledTimes(1);
$this->adapter->count(); $this->adapter->count();
} }
} }

View File

@ -11,12 +11,13 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
* @param int|null $limit * @param int|null $limit
* @param int|null $offset * @param int|null $offset
* @param string|null $searchTerm * @param string|null $searchTerm
* @param array $tags
* @param string|array|null $orderBy * @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 = $this->createListQueryBuilder($searchTerm, $tags);
$qb->select('s'); $qb->select('s');
if (isset($limit)) { if (isset($limit)) {
@ -43,11 +44,12 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
* Counts the number of elements in a list using provided filtering data * Counts the number of elements in a list using provided filtering data
* *
* @param null|string $searchTerm * @param null|string $searchTerm
* @param array $tags
* @return int * @return int
*/ */
public function countList($searchTerm = null) public function countList($searchTerm = null, array $tags = [])
{ {
$qb = $this->createListQueryBuilder($searchTerm); $qb = $this->createListQueryBuilder($searchTerm, $tags);
$qb->select('COUNT(s)'); $qb->select('COUNT(s)');
return (int) $qb->getQuery()->getSingleScalarResult(); return (int) $qb->getQuery()->getSingleScalarResult();
@ -55,12 +57,14 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
/** /**
* @param null|string $searchTerm * @param null|string $searchTerm
* @param array $tags
* @return QueryBuilder * @return QueryBuilder
*/ */
protected function createListQueryBuilder($searchTerm = null) protected function createListQueryBuilder($searchTerm = null, array $tags = [])
{ {
$qb = $this->getEntityManager()->createQueryBuilder(); $qb = $this->getEntityManager()->createQueryBuilder();
$qb->from(ShortUrl::class, 's'); $qb->from(ShortUrl::class, 's');
$qb->where('1=1');
// Apply search term to every searchable field if not empty // Apply search term to every searchable field if not empty
if (! empty($searchTerm)) { if (! empty($searchTerm)) {
@ -70,11 +74,17 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
]; ];
// Unpack and apply search conditions // Unpack and apply search conditions
$qb->where($qb->expr()->orX(...$conditions)); $qb->andWhere($qb->expr()->orX(...$conditions));
$searchTerm = '%' . $searchTerm . '%'; $searchTerm = '%' . $searchTerm . '%';
$qb->setParameter('searchPattern', $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; return $qb;
} }
} }

View File

@ -33,13 +33,14 @@ class ShortUrlService implements ShortUrlServiceInterface
/** /**
* @param int $page * @param int $page
* @param string $searchQuery * @param string $searchQuery
* @param array $tags
* @return ShortUrl[]|Paginator * @return ShortUrl[]|Paginator
*/ */
public function listShortUrls($page = 1, $searchQuery = null) public function listShortUrls($page = 1, $searchQuery = null, array $tags = [])
{ {
/** @var ShortUrlRepository $repo */ /** @var ShortUrlRepository $repo */
$repo = $this->em->getRepository(ShortUrl::class); $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) $paginator->setItemCountPerPage(PaginableRepositoryAdapter::ITEMS_PER_PAGE)
->setCurrentPageNumber($page); ->setCurrentPageNumber($page);

View File

@ -10,9 +10,10 @@ interface ShortUrlServiceInterface
/** /**
* @param int $page * @param int $page
* @param string $searchQuery * @param string $searchQuery
* @param array $tags
* @return ShortUrl[]|Paginator * @return ShortUrl[]|Paginator
*/ */
public function listShortUrls($page = 1, $searchQuery = null); public function listShortUrls($page = 1, $searchQuery = null, array $tags = []);
/** /**
* @param string $shortCode * @param string $shortCode

View File

@ -74,6 +74,7 @@ class ListShortcodesAction extends AbstractRestAction
return [ return [
isset($query['page']) ? $query['page'] : 1, isset($query['page']) ? $query['page'] : 1,
isset($query['searchTerm']) ? $query['searchTerm'] : null, isset($query['searchTerm']) ? $query['searchTerm'] : null,
isset($query['tags']) ? $query['tags'] : [],
]; ];
} }
} }

View File

@ -34,7 +34,7 @@ class ListShortcodesActionTest extends TestCase
public function properListReturnsSuccessResponse() public function properListReturnsSuccessResponse()
{ {
$page = 3; $page = 3;
$this->service->listShortUrls($page, null)->willReturn(new Paginator(new ArrayAdapter())) $this->service->listShortUrls($page, null, [])->willReturn(new Paginator(new ArrayAdapter()))
->shouldBeCalledTimes(1); ->shouldBeCalledTimes(1);
$response = $this->action->__invoke( $response = $this->action->__invoke(
@ -52,7 +52,7 @@ class ListShortcodesActionTest extends TestCase
public function anExceptionsReturnsErrorResponse() public function anExceptionsReturnsErrorResponse()
{ {
$page = 3; $page = 3;
$this->service->listShortUrls($page, null)->willThrow(\Exception::class) $this->service->listShortUrls($page, null, [])->willThrow(\Exception::class)
->shouldBeCalledTimes(1); ->shouldBeCalledTimes(1);
$response = $this->action->__invoke( $response = $this->action->__invoke(