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",
"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"
}

Binary file not shown.

View file

@ -1,8 +1,8 @@
msgid ""
msgstr ""
"Project-Id-Version: Shlink 1.0\n"
"POT-Creation-Date: 2016-08-21 18:16+0200\n"
"PO-Revision-Date: 2016-08-21 18:16+0200\n"
"POT-Creation-Date: 2016-10-22 13:14+0200\n"
"PO-Revision-Date: 2016-10-22 13:15+0200\n"
"Last-Translator: Alejandro Celaya <alejandro@alejandrocelaya.com>\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"

View file

@ -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);

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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 = []);
}

View file

@ -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();
}
}

View file

@ -11,12 +11,13 @@ 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 = $this->createListQueryBuilder($searchTerm, $tags);
$qb->select('s');
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
*
* @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 = $this->createListQueryBuilder($searchTerm, $tags);
$qb->select('COUNT(s)');
return (int) $qb->getQuery()->getSingleScalarResult();
@ -55,12 +57,14 @@ 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');
$qb->where('1=1');
// Apply search term to every searchable field if not empty
if (! empty($searchTerm)) {
@ -70,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;
}
}

View file

@ -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);

View file

@ -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

View file

@ -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'] : [],
];
}
}

View file

@ -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([