From 72e56d271d6babdfee5620a85b68ba549cc46576 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 22 May 2022 19:22:29 +0200 Subject: [PATCH] Created tags visits command, with abstract class wrapping common logic for visits lists commands --- module/CLI/config/cli.config.php | 1 + module/CLI/config/dependencies.config.php | 2 + .../src/Command/ShortUrl/GetVisitsCommand.php | 50 ++-------------- .../CLI/src/Command/Tag/RenameTagCommand.php | 2 +- .../CLI/src/Command/Tag/TagVisitsCommand.php | 31 ++++++++++ .../Visit/AbstractVisitsListCommand.php | 57 +++++++++++++++++++ .../Command/ShortUrl/GetVisitsCommandTest.php | 3 +- module/Core/src/Entity/Visit.php | 3 +- module/Core/src/Entity/VisitLocation.php | 3 +- .../src/Visit/Model/UnknownVisitLocation.php | 41 ------------- .../Visit/Model/VisitLocationInterface.php | 18 ------ 11 files changed, 102 insertions(+), 109 deletions(-) create mode 100644 module/CLI/src/Command/Tag/TagVisitsCommand.php create mode 100644 module/CLI/src/Command/Visit/AbstractVisitsListCommand.php delete mode 100644 module/Core/src/Visit/Model/UnknownVisitLocation.php delete mode 100644 module/Core/src/Visit/Model/VisitLocationInterface.php diff --git a/module/CLI/config/cli.config.php b/module/CLI/config/cli.config.php index 2b5b5afd..5e24703a 100644 --- a/module/CLI/config/cli.config.php +++ b/module/CLI/config/cli.config.php @@ -24,6 +24,7 @@ return [ Command\Tag\ListTagsCommand::NAME => Command\Tag\ListTagsCommand::class, Command\Tag\RenameTagCommand::NAME => Command\Tag\RenameTagCommand::class, Command\Tag\DeleteTagsCommand::NAME => Command\Tag\DeleteTagsCommand::class, + Command\Tag\TagVisitsCommand::NAME => Command\Tag\TagVisitsCommand::class, Command\Domain\ListDomainsCommand::NAME => Command\Domain\ListDomainsCommand::class, Command\Domain\DomainRedirectsCommand::NAME => Command\Domain\DomainRedirectsCommand::class, diff --git a/module/CLI/config/dependencies.config.php b/module/CLI/config/dependencies.config.php index 137bdd7a..b394cb8f 100644 --- a/module/CLI/config/dependencies.config.php +++ b/module/CLI/config/dependencies.config.php @@ -55,6 +55,7 @@ return [ Command\Tag\ListTagsCommand::class => ConfigAbstractFactory::class, Command\Tag\RenameTagCommand::class => ConfigAbstractFactory::class, Command\Tag\DeleteTagsCommand::class => ConfigAbstractFactory::class, + Command\Tag\TagVisitsCommand::class => ConfigAbstractFactory::class, Command\Db\CreateDatabaseCommand::class => ConfigAbstractFactory::class, Command\Db\MigrateDatabaseCommand::class => ConfigAbstractFactory::class, @@ -102,6 +103,7 @@ return [ Command\Tag\ListTagsCommand::class => [TagService::class], Command\Tag\RenameTagCommand::class => [TagService::class], Command\Tag\DeleteTagsCommand::class => [TagService::class], + Command\Tag\TagVisitsCommand::class => [Visit\VisitsStatsHelper::class], Command\Domain\ListDomainsCommand::class => [DomainService::class], Command\Domain\DomainRedirectsCommand::class => [DomainService::class], diff --git a/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php b/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php index bb2f0229..7bcbd367 100644 --- a/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php +++ b/module/CLI/src/Command/ShortUrl/GetVisitsCommand.php @@ -4,34 +4,21 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\ShortUrl; -use Shlinkio\Shlink\CLI\Command\Util\AbstractWithDateRangeCommand; -use Shlinkio\Shlink\CLI\Util\ExitCodes; -use Shlinkio\Shlink\CLI\Util\ShlinkTable; -use Shlinkio\Shlink\Core\Entity\Visit; +use Shlinkio\Shlink\CLI\Command\Visit\AbstractVisitsListCommand; +use Shlinkio\Shlink\Common\Paginator\Paginator; +use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\Model\VisitsParams; -use Shlinkio\Shlink\Core\Visit\Model\UnknownVisitLocation; -use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface; 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\Style\SymfonyStyle; -use function Functional\map; -use function Functional\select_keys; -use function Shlinkio\Shlink\Common\buildDateRange; -use function sprintf; - -class GetVisitsCommand extends AbstractWithDateRangeCommand +class GetVisitsCommand extends AbstractVisitsListCommand { public const NAME = 'short-url:visits'; - public function __construct(private VisitsStatsHelperInterface $visitsHelper) - { - parent::__construct(); - } - protected function doConfigure(): void { $this @@ -41,16 +28,6 @@ class GetVisitsCommand extends AbstractWithDateRangeCommand ->addOption('domain', 'd', InputOption::VALUE_REQUIRED, 'The domain for the short code.'); } - protected function getStartDateDesc(string $optionName): string - { - return sprintf('Allows to filter visits, returning only those older than "%s".', $optionName); - } - - protected function getEndDateDesc(string $optionName): string - { - return sprintf('Allows to filter visits, returning only those newer than "%s".', $optionName); - } - protected function interact(InputInterface $input, OutputInterface $output): void { $shortCode = $input->getArgument('shortCode'); @@ -65,24 +42,9 @@ class GetVisitsCommand extends AbstractWithDateRangeCommand } } - protected function execute(InputInterface $input, OutputInterface $output): ?int + protected function getVisitsPaginator(InputInterface $input, DateRange $dateRange): Paginator { $identifier = ShortUrlIdentifier::fromCli($input); - $startDate = $this->getStartDateOption($input, $output); - $endDate = $this->getEndDateOption($input, $output); - - $paginator = $this->visitsHelper->visitsForShortUrl( - $identifier, - new VisitsParams(buildDateRange($startDate, $endDate)), - ); - - $rows = map($paginator->getCurrentPageResults(), function (Visit $visit) { - $rowData = $visit->jsonSerialize(); - $rowData['country'] = ($visit->getVisitLocation() ?? new UnknownVisitLocation())->getCountryName(); - return select_keys($rowData, ['referer', 'date', 'userAgent', 'country']); - }); - ShlinkTable::default($output)->render(['Referer', 'Date', 'User agent', 'Country'], $rows); - - return ExitCodes::EXIT_SUCCESS; + return $this->visitsHelper->visitsForShortUrl($identifier, new VisitsParams($dateRange)); } } diff --git a/module/CLI/src/Command/Tag/RenameTagCommand.php b/module/CLI/src/Command/Tag/RenameTagCommand.php index 23c1568d..85377a18 100644 --- a/module/CLI/src/Command/Tag/RenameTagCommand.php +++ b/module/CLI/src/Command/Tag/RenameTagCommand.php @@ -19,7 +19,7 @@ class RenameTagCommand extends Command { public const NAME = 'tag:rename'; - public function __construct(private TagServiceInterface $tagService) + public function __construct(private readonly TagServiceInterface $tagService) { parent::__construct(); } diff --git a/module/CLI/src/Command/Tag/TagVisitsCommand.php b/module/CLI/src/Command/Tag/TagVisitsCommand.php new file mode 100644 index 00000000..a50cb0da --- /dev/null +++ b/module/CLI/src/Command/Tag/TagVisitsCommand.php @@ -0,0 +1,31 @@ +setName(self::NAME) + ->setDescription('Returns the list of visits for provided tag.') + ->addArgument('tag', InputArgument::REQUIRED, 'The tag which visits we want to get.'); + } + + protected function getVisitsPaginator(InputInterface $input, DateRange $dateRange): Paginator + { + $tag = $input->getArgument('tag'); + return $this->visitsHelper->visitsForTag($tag, new VisitsParams($dateRange)); + } +} diff --git a/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php b/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php new file mode 100644 index 00000000..3ed69c7f --- /dev/null +++ b/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php @@ -0,0 +1,57 @@ +getStartDateOption($input, $output); + $endDate = $this->getEndDateOption($input, $output); + $paginator = $this->getVisitsPaginator($input, buildDateRange($startDate, $endDate)); + + $rows = map($paginator->getCurrentPageResults(), function (Visit $visit) { + $rowData = $visit->jsonSerialize(); + $rowData['country'] = $visit->getVisitLocation()?->getCountryName() ?? 'Unknown'; + $rowData['city'] = $visit->getVisitLocation()?->getCityName() ?? 'Unknown'; + return select_keys($rowData, ['referer', 'date', 'userAgent', 'country', 'city']); + }); + ShlinkTable::default($output)->render(['Referer', 'Date', 'User agent', 'Country', 'City'], $rows); + + return ExitCodes::EXIT_SUCCESS; + } + + abstract protected function getVisitsPaginator(InputInterface $input, DateRange $dateRange): Paginator; +} diff --git a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php index 7a884c89..daea7b7f 100644 --- a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php @@ -106,7 +106,7 @@ class GetVisitsCommandTest extends TestCase )->willReturn( new Paginator(new ArrayAdapter([ Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('bar', 'foo', '', ''))->locate( - VisitLocation::fromGeolocation(new Location('', 'Spain', '', '', 0, 0, '')), + VisitLocation::fromGeolocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')), ), ])), )->shouldBeCalledOnce(); @@ -115,6 +115,7 @@ class GetVisitsCommandTest extends TestCase $output = $this->commandTester->getDisplay(); self::assertStringContainsString('foo', $output); self::assertStringContainsString('Spain', $output); + self::assertStringContainsString('Madrid', $output); self::assertStringContainsString('bar', $output); } } diff --git a/module/Core/src/Entity/Visit.php b/module/Core/src/Entity/Visit.php index 23a518ca..9bff9db9 100644 --- a/module/Core/src/Entity/Visit.php +++ b/module/Core/src/Entity/Visit.php @@ -10,7 +10,6 @@ use Shlinkio\Shlink\Common\Entity\AbstractEntity; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Common\Util\IpAddress; use Shlinkio\Shlink\Core\Model\Visitor; -use Shlinkio\Shlink\Core\Visit\Model\VisitLocationInterface; use Shlinkio\Shlink\Core\Visit\Model\VisitType; use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit; @@ -119,7 +118,7 @@ class Visit extends AbstractEntity implements JsonSerializable return $this->shortUrl; } - public function getVisitLocation(): ?VisitLocationInterface + public function getVisitLocation(): ?VisitLocation { return $this->visitLocation; } diff --git a/module/Core/src/Entity/VisitLocation.php b/module/Core/src/Entity/VisitLocation.php index 594126a7..5ab781de 100644 --- a/module/Core/src/Entity/VisitLocation.php +++ b/module/Core/src/Entity/VisitLocation.php @@ -5,11 +5,10 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Entity; use Shlinkio\Shlink\Common\Entity\AbstractEntity; -use Shlinkio\Shlink\Core\Visit\Model\VisitLocationInterface; use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisitLocation; use Shlinkio\Shlink\IpGeolocation\Model\Location; -class VisitLocation extends AbstractEntity implements VisitLocationInterface +class VisitLocation extends AbstractEntity { private string $countryCode; private string $countryName; diff --git a/module/Core/src/Visit/Model/UnknownVisitLocation.php b/module/Core/src/Visit/Model/UnknownVisitLocation.php deleted file mode 100644 index b8926bd5..00000000 --- a/module/Core/src/Visit/Model/UnknownVisitLocation.php +++ /dev/null @@ -1,41 +0,0 @@ - 'Unknown', - 'countryName' => 'Unknown', - 'regionName' => 'Unknown', - 'cityName' => 'Unknown', - 'latitude' => 0.0, - 'longitude' => 0.0, - 'timezone' => 'Unknown', - ]; - } -} diff --git a/module/Core/src/Visit/Model/VisitLocationInterface.php b/module/Core/src/Visit/Model/VisitLocationInterface.php deleted file mode 100644 index 9a296a28..00000000 --- a/module/Core/src/Visit/Model/VisitLocationInterface.php +++ /dev/null @@ -1,18 +0,0 @@ -