From d97287ab0c91e5825259b3edb9a98123bf1dfc33 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 21 Jul 2016 09:36:38 +0200 Subject: [PATCH 1/6] Added option to filter by date the visits list --- module/CLI/src/Command/GetVisitsCommand.php | 6 ++- module/Common/src/Util/DateRange.php | 36 ++++++++++++++++++ .../Core/src/Repository/VisitRepository.php | 37 +++++++++++++++++++ .../Repository/VisitRepositoryInterface.php | 9 +++++ module/Core/src/Service/VisitsTracker.php | 18 ++++----- .../src/Service/VisitsTrackerInterface.php | 9 +++-- 6 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 module/Common/src/Util/DateRange.php diff --git a/module/CLI/src/Command/GetVisitsCommand.php b/module/CLI/src/Command/GetVisitsCommand.php index ee4cf3ad..ae6dc4b7 100644 --- a/module/CLI/src/Command/GetVisitsCommand.php +++ b/module/CLI/src/Command/GetVisitsCommand.php @@ -70,7 +70,11 @@ class GetVisitsCommand extends Command ]); foreach ($visits as $row) { - $table->addRow(array_values($row->jsonSerialize())); + $rowData = $row->jsonSerialize(); + // Unset location info + unset($rowData['visitLocation']); + + $table->addRow(array_values($rowData)); } $table->render(); } diff --git a/module/Common/src/Util/DateRange.php b/module/Common/src/Util/DateRange.php new file mode 100644 index 00000000..8e156a20 --- /dev/null +++ b/module/Common/src/Util/DateRange.php @@ -0,0 +1,36 @@ +startDate = $startDate; + $this->endDate = $endDate; + } + + /** + * @return \DateTimeInterface + */ + public function getStartDate() + { + return $this->startDate; + } + + /** + * @return \DateTimeInterface + */ + public function getEndDate() + { + return $this->endDate; + } +} diff --git a/module/Core/src/Repository/VisitRepository.php b/module/Core/src/Repository/VisitRepository.php index ef7d7935..7b98c5d5 100644 --- a/module/Core/src/Repository/VisitRepository.php +++ b/module/Core/src/Repository/VisitRepository.php @@ -2,6 +2,8 @@ namespace Shlinkio\Shlink\Core\Repository; use Doctrine\ORM\EntityRepository; +use Shlinkio\Shlink\Common\Util\DateRange; +use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; class VisitRepository extends EntityRepository implements VisitRepositoryInterface @@ -16,4 +18,39 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa return $qb->getQuery()->getResult(); } + + /** + * @param ShortUrl|int $shortUrl + * @param DateRange|null $dateRange + * @return Visit[] + */ + public function findVisitsByShortUrl($shortUrl, DateRange $dateRange = null) + { + $shortUrl = $shortUrl instanceof ShortUrl + ? $shortUrl + : $this->getEntityManager()->find(ShortUrl::class, $shortUrl); + if (! isset($dateRange)) { + $startDate = $shortUrl->getDateCreated(); + $endDate = clone $startDate; + $endDate->add(new \DateInterval('P2D')); + $dateRange = new DateRange($startDate, $endDate); + } + + $qb = $this->createQueryBuilder('v'); + $qb->where($qb->expr()->eq('v.shortUrl', ':shortUrl')) + ->setParameter('shortUrl', $shortUrl) + ->orderBy('v.date', 'DESC') ; + + // Apply date range filtering + if (! empty($dateRange->getStartDate())) { + $qb->andWhere($qb->expr()->gte('v.date', ':startDate')) + ->setParameter('startDate', $dateRange->getStartDate()); + } + if (! empty($dateRange->getEndDate())) { + $qb->andWhere($qb->expr()->lte('v.date', ':endDate')) + ->setParameter('endDate', $dateRange->getEndDate()); + } + + return $qb->getQuery()->getResult(); + } } diff --git a/module/Core/src/Repository/VisitRepositoryInterface.php b/module/Core/src/Repository/VisitRepositoryInterface.php index 6534d7ea..c65f495d 100644 --- a/module/Core/src/Repository/VisitRepositoryInterface.php +++ b/module/Core/src/Repository/VisitRepositoryInterface.php @@ -2,6 +2,8 @@ namespace Shlinkio\Shlink\Core\Repository; use Doctrine\Common\Persistence\ObjectRepository; +use Shlinkio\Shlink\Common\Util\DateRange; +use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; interface VisitRepositoryInterface extends ObjectRepository @@ -10,4 +12,11 @@ interface VisitRepositoryInterface extends ObjectRepository * @return Visit[] */ public function findUnlocatedVisits(); + + /** + * @param ShortUrl|int $shortUrl + * @param DateRange|null $dateRange + * @return Visit[] + */ + public function findVisitsByShortUrl($shortUrl, DateRange $dateRange = null); } diff --git a/module/Core/src/Service/VisitsTracker.php b/module/Core/src/Service/VisitsTracker.php index 2fd053c1..87187aad 100644 --- a/module/Core/src/Service/VisitsTracker.php +++ b/module/Core/src/Service/VisitsTracker.php @@ -4,9 +4,10 @@ namespace Shlinkio\Shlink\Core\Service; use Acelaya\ZsmAnnotatedServices\Annotation\Inject; use Doctrine\ORM\EntityManagerInterface; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; +use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; -use Zend\Paginator\Paginator; +use Shlinkio\Shlink\Core\Repository\VisitRepository; class VisitsTracker implements VisitsTrackerInterface { @@ -62,12 +63,13 @@ class VisitsTracker implements VisitsTrackerInterface } /** - * Returns the visits on certain shortcode + * Returns the visits on certain short code * * @param $shortCode - * @return Paginator|Visit[] + * @param DateRange $dateRange + * @return Visit[] */ - public function info($shortCode) + public function info($shortCode, DateRange $dateRange = null) { /** @var ShortUrl $shortUrl */ $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([ @@ -77,10 +79,8 @@ class VisitsTracker implements VisitsTrackerInterface throw new InvalidArgumentException(sprintf('Short code "%s" not found', $shortCode)); } - return $this->em->getRepository(Visit::class)->findBy([ - 'shortUrl' => $shortUrl, - ], [ - 'date' => 'DESC' - ]); + /** @var VisitRepository $repo */ + $repo = $this->em->getRepository(Visit::class); + return $repo->findVisitsByShortUrl($shortUrl, $dateRange); } } diff --git a/module/Core/src/Service/VisitsTrackerInterface.php b/module/Core/src/Service/VisitsTrackerInterface.php index 04966403..cec254d3 100644 --- a/module/Core/src/Service/VisitsTrackerInterface.php +++ b/module/Core/src/Service/VisitsTrackerInterface.php @@ -1,8 +1,8 @@ Date: Thu, 21 Jul 2016 09:46:12 +0200 Subject: [PATCH 2/6] Fixed typo --- module/CLI/src/Command/GetVisitsCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/CLI/src/Command/GetVisitsCommand.php b/module/CLI/src/Command/GetVisitsCommand.php index ae6dc4b7..43d87de5 100644 --- a/module/CLI/src/Command/GetVisitsCommand.php +++ b/module/CLI/src/Command/GetVisitsCommand.php @@ -65,7 +65,7 @@ class GetVisitsCommand extends Command $table->setHeaders([ 'Referer', 'Date', - 'Temote Address', + 'Remote Address', 'User agent', ]); From 45d194acedb41c22f68283b93ee700db15c30d98 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 21 Jul 2016 09:58:33 +0200 Subject: [PATCH 3/6] Added option to filter by date in shortcode:views CLI command --- module/CLI/src/Command/GetVisitsCommand.php | 31 +++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/module/CLI/src/Command/GetVisitsCommand.php b/module/CLI/src/Command/GetVisitsCommand.php index 43d87de5..e7221e5e 100644 --- a/module/CLI/src/Command/GetVisitsCommand.php +++ b/module/CLI/src/Command/GetVisitsCommand.php @@ -2,6 +2,7 @@ namespace Shlinkio\Shlink\CLI\Command; use Acelaya\ZsmAnnotatedServices\Annotation\Inject; +use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Service\VisitsTracker; use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface; use Symfony\Component\Console\Command\Command; @@ -9,6 +10,7 @@ use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; @@ -35,7 +37,19 @@ class GetVisitsCommand extends Command { $this->setName('shortcode:visits') ->setDescription('Returns the detailed visits information for provided short code') - ->addArgument('shortCode', InputArgument::REQUIRED, 'The short code which visits we want to get'); + ->addArgument('shortCode', InputArgument::REQUIRED, 'The short code which visits we want to get') + ->addOption( + 'startDate', + 's', + InputOption::VALUE_OPTIONAL, + 'Allows to filter visits, returning only those older than start date' + ) + ->addOption( + 'endDate', + 'e', + InputOption::VALUE_OPTIONAL, + 'Allows to filter visits, returning only those newer than end date' + ); } public function interact(InputInterface $input, OutputInterface $output) @@ -60,7 +74,10 @@ class GetVisitsCommand extends Command public function execute(InputInterface $input, OutputInterface $output) { $shortCode = $input->getArgument('shortCode'); - $visits = $this->visitsTracker->info($shortCode); + $startDate = $this->getDateOption($input, 'startDate'); + $endDate = $this->getDateOption($input, 'endDate'); + + $visits = $this->visitsTracker->info($shortCode, new DateRange($startDate, $endDate)); $table = new Table($output); $table->setHeaders([ 'Referer', @@ -78,4 +95,14 @@ class GetVisitsCommand extends Command } $table->render(); } + + protected function getDateOption(InputInterface $input, $key) + { + $value = $input->getOption($key); + if (isset($value)) { + $value = new \DateTime($value); + } + + return $value; + } } From bdd2d6f8b245c986e60bd4b1a967d29e204f7bb3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 21 Jul 2016 10:03:37 +0200 Subject: [PATCH 4/6] Improved DateRange to check if both wrapped dates are empty --- module/Common/src/Util/DateRange.php | 8 ++++++++ module/Core/src/Repository/VisitRepository.php | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/module/Common/src/Util/DateRange.php b/module/Common/src/Util/DateRange.php index 8e156a20..c87f402a 100644 --- a/module/Common/src/Util/DateRange.php +++ b/module/Common/src/Util/DateRange.php @@ -33,4 +33,12 @@ class DateRange { return $this->endDate; } + + /** + * @return bool + */ + public function isEmpty() + { + return is_null($this->startDate) && is_null($this->endDate); + } } diff --git a/module/Core/src/Repository/VisitRepository.php b/module/Core/src/Repository/VisitRepository.php index 7b98c5d5..743427f6 100644 --- a/module/Core/src/Repository/VisitRepository.php +++ b/module/Core/src/Repository/VisitRepository.php @@ -29,7 +29,7 @@ class VisitRepository extends EntityRepository implements VisitRepositoryInterfa $shortUrl = $shortUrl instanceof ShortUrl ? $shortUrl : $this->getEntityManager()->find(ShortUrl::class, $shortUrl); - if (! isset($dateRange)) { + if (! isset($dateRange) || $dateRange->isEmpty()) { $startDate = $shortUrl->getDateCreated(); $endDate = clone $startDate; $endDate->add(new \DateInterval('P2D')); From 0ef9db0bdfd3c13ac71a51cc97125c0e8f5f15be Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 21 Jul 2016 10:13:09 +0200 Subject: [PATCH 5/6] Added option to filter by date in visits REST endpoint --- data/docs/rest.md | 3 +++ .../Rest/src/Action/GetVisitsMiddleware.php | 21 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/data/docs/rest.md b/data/docs/rest.md index f93f6c48..d006c6c5 100644 --- a/data/docs/rest.md +++ b/data/docs/rest.md @@ -225,6 +225,9 @@ Posible errors: * `GET` -> `/rest/visits/{shortCode}` * Route params: * shortCode: `string` -> The shortCode from which we eant to get the visits. +* Query params: + * startDate: `string` -> If provided, only visits older that this date will be returned + * endDate: `string` -> If provided, only visits newer that this date will be returned * Headers: * X-Auth-Token: `string` -> The token provided in the authentication request diff --git a/module/Rest/src/Action/GetVisitsMiddleware.php b/module/Rest/src/Action/GetVisitsMiddleware.php index 148053ee..3e5bfbaf 100644 --- a/module/Rest/src/Action/GetVisitsMiddleware.php +++ b/module/Rest/src/Action/GetVisitsMiddleware.php @@ -5,6 +5,7 @@ use Acelaya\ZsmAnnotatedServices\Annotation\Inject; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; +use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Service\VisitsTracker; use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface; use Shlinkio\Shlink\Rest\Util\RestUtils; @@ -37,14 +38,15 @@ class GetVisitsMiddleware extends AbstractRestMiddleware public function dispatch(Request $request, Response $response, callable $out = null) { $shortCode = $request->getAttribute('shortCode'); + $startDate = $this->getDateQueryParam($request, 'startDate'); + $endDate = $this->getDateQueryParam($request, 'endDate'); try { - $visits = $this->visitsTracker->info($shortCode); + $visits = $this->visitsTracker->info($shortCode, new DateRange($startDate, $endDate)); return new JsonResponse([ 'visits' => [ 'data' => $visits, -// 'pagination' => [], ] ]); } catch (InvalidArgumentException $e) { @@ -59,4 +61,19 @@ class GetVisitsMiddleware extends AbstractRestMiddleware ], 500); } } + + /** + * @param Request $request + * @param $key + * @return \DateTime|null + */ + protected function getDateQueryParam(Request $request, $key) + { + $query = $request->getQueryParams(); + if (! isset($query[$key]) || empty($query[$key])) { + return null; + } + + return new \DateTime($query[$key]); + } } From 3ba51c5390b8ff6c2a637a77dd3dc182a0a98f30 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 21 Jul 2016 10:16:36 +0200 Subject: [PATCH 6/6] Improved visits REST endpoint path --- data/docs/rest.md | 2 +- module/Rest/config/routes.config.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/docs/rest.md b/data/docs/rest.md index d006c6c5..09147518 100644 --- a/data/docs/rest.md +++ b/data/docs/rest.md @@ -222,7 +222,7 @@ Posible errors: **REQUEST** -* `GET` -> `/rest/visits/{shortCode}` +* `GET` -> `/rest/short-codes/{shortCode}/visits` * Route params: * shortCode: `string` -> The shortCode from which we eant to get the visits. * Query params: diff --git a/module/Rest/config/routes.config.php b/module/Rest/config/routes.config.php index e8abb6fe..24226840 100644 --- a/module/Rest/config/routes.config.php +++ b/module/Rest/config/routes.config.php @@ -30,7 +30,7 @@ return [ ], [ 'name' => 'rest-get-visits', - 'path' => '/rest/visits/{shortCode}', + 'path' => '/rest/short-codes/{shortCode}/visits', 'middleware' => Action\GetVisitsMiddleware::class, 'allowed_methods' => ['GET', 'OPTIONS'], ],