Added logic to resolve extra columns on visits commands

This commit is contained in:
Alejandro Celaya 2022-05-23 21:19:59 +02:00
parent 00002b1e24
commit 353ac0fc0c
8 changed files with 123 additions and 10 deletions

View file

@ -99,7 +99,7 @@ return [
LockFactory::class,
],
Command\Visit\GetOrphanVisitsCommand::class => [Visit\VisitsStatsHelper::class],
Command\Visit\GetNonOrphanVisitsCommand::class => [Visit\VisitsStatsHelper::class],
Command\Visit\GetNonOrphanVisitsCommand::class => [Visit\VisitsStatsHelper::class, ShortUrlStringifier::class],
Command\Api\GenerateKeyCommand::class => [ApiKeyService::class, ApiKey\RoleResolver::class],
Command\Api\DisableKeyCommand::class => [ApiKeyService::class],
@ -108,11 +108,11 @@ return [
Command\Tag\ListTagsCommand::class => [TagService::class],
Command\Tag\RenameTagCommand::class => [TagService::class],
Command\Tag\DeleteTagsCommand::class => [TagService::class],
Command\Tag\GetTagVisitsCommand::class => [Visit\VisitsStatsHelper::class],
Command\Tag\GetTagVisitsCommand::class => [Visit\VisitsStatsHelper::class, ShortUrlStringifier::class],
Command\Domain\ListDomainsCommand::class => [DomainService::class],
Command\Domain\DomainRedirectsCommand::class => [DomainService::class],
Command\Domain\GetDomainVisitsCommand::class => [Visit\VisitsStatsHelper::class],
Command\Domain\GetDomainVisitsCommand::class => [Visit\VisitsStatsHelper::class, ShortUrlStringifier::class],
Command\Db\CreateDatabaseCommand::class => [
LockFactory::class,

View file

@ -7,7 +7,10 @@ namespace Shlinkio\Shlink\CLI\Command\Domain;
use Shlinkio\Shlink\CLI\Command\Visit\AbstractVisitsListCommand;
use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Model\VisitsParams;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@ -15,6 +18,13 @@ class GetDomainVisitsCommand extends AbstractVisitsListCommand
{
public const NAME = 'domain:visits';
public function __construct(
VisitsStatsHelperInterface $visitsHelper,
private readonly ShortUrlStringifierInterface $shortUrlStringifier,
) {
parent::__construct($visitsHelper);
}
protected function doConfigure(): void
{
$this
@ -28,4 +38,13 @@ class GetDomainVisitsCommand extends AbstractVisitsListCommand
$domain = $input->getArgument('domain');
return $this->visitsHelper->visitsForDomain($domain, new VisitsParams($dateRange));
}
/**
* @return array<string, string>
*/
protected function mapExtraFields(Visit $visit): array
{
$shortUrl = $visit->getShortUrl();
return $shortUrl === null ? [] : ['shortUrl' => $this->shortUrlStringifier->stringify($shortUrl)];
}
}

View file

@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
use Shlinkio\Shlink\CLI\Command\Visit\AbstractVisitsListCommand;
use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\VisitsParams;
use Symfony\Component\Console\Input\InputArgument;
@ -47,4 +48,12 @@ class GetShortUrlVisitsCommand extends AbstractVisitsListCommand
$identifier = ShortUrlIdentifier::fromCli($input);
return $this->visitsHelper->visitsForShortUrl($identifier, new VisitsParams($dateRange));
}
/**
* @return array<string, string>
*/
protected function mapExtraFields(Visit $visit): array
{
return [];
}
}

View file

@ -7,7 +7,10 @@ namespace Shlinkio\Shlink\CLI\Command\Tag;
use Shlinkio\Shlink\CLI\Command\Visit\AbstractVisitsListCommand;
use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Model\VisitsParams;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@ -15,6 +18,13 @@ class GetTagVisitsCommand extends AbstractVisitsListCommand
{
public const NAME = 'tag:visits';
public function __construct(
VisitsStatsHelperInterface $visitsHelper,
private readonly ShortUrlStringifierInterface $shortUrlStringifier,
) {
parent::__construct($visitsHelper);
}
protected function doConfigure(): void
{
$this
@ -28,4 +38,13 @@ class GetTagVisitsCommand extends AbstractVisitsListCommand
$tag = $input->getArgument('tag');
return $this->visitsHelper->visitsForTag($tag, new VisitsParams($dateRange));
}
/**
* @return array<string, string>
*/
protected function mapExtraFields(Visit $visit): array
{
$shortUrl = $visit->getShortUrl();
return $shortUrl === null ? [] : ['shortUrl' => $this->shortUrlStringifier->stringify($shortUrl)];
}
}

View file

@ -14,9 +14,11 @@ use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use function array_keys;
use function Functional\map;
use function Functional\select_keys;
use function Shlinkio\Shlink\Common\buildDateRange;
use function Shlinkio\Shlink\Core\camelCaseToHumanFriendly;
use function sprintf;
abstract class AbstractVisitsListCommand extends AbstractWithDateRangeCommand
@ -41,17 +43,41 @@ abstract class AbstractVisitsListCommand extends AbstractWithDateRangeCommand
$startDate = $this->getStartDateOption($input, $output);
$endDate = $this->getEndDateOption($input, $output);
$paginator = $this->getVisitsPaginator($input, buildDateRange($startDate, $endDate));
[$rows, $headers] = $this->resolveRowsAndHeaders($paginator);
$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);
ShlinkTable::default($output)->render($headers, $rows);
return ExitCodes::EXIT_SUCCESS;
}
private function resolveRowsAndHeaders(Paginator $paginator): array
{
$extraKeys = [];
$rows = map($paginator->getCurrentPageResults(), function (Visit $visit) use (&$extraKeys) {
$extraFields = $this->mapExtraFields($visit);
$extraKeys = array_keys($extraFields);
$rowData = [
...$visit->jsonSerialize(),
'country' => $visit->getVisitLocation()?->getCountryName() ?? 'Unknown',
'city' => $visit->getVisitLocation()?->getCityName() ?? 'Unknown',
...$extraFields,
];
return select_keys($rowData, ['referer', 'date', 'userAgent', 'country', 'city', ...$extraKeys]);
});
$extra = map($extraKeys, camelCaseToHumanFriendly(...));
return [
$rows,
['Referer', 'Date', 'User agent', 'Country', 'City', ...$extra],
];
}
abstract protected function getVisitsPaginator(InputInterface $input, DateRange $dateRange): Paginator;
/**
* @return array<string, string>
*/
abstract protected function mapExtraFields(Visit $visit): array;
}

View file

@ -6,13 +6,23 @@ namespace Shlinkio\Shlink\CLI\Command\Visit;
use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Model\VisitsParams;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
use Symfony\Component\Console\Input\InputInterface;
class GetNonOrphanVisitsCommand extends AbstractVisitsListCommand
{
public const NAME = 'visit:non-orphan';
public function __construct(
VisitsStatsHelperInterface $visitsHelper,
private readonly ShortUrlStringifierInterface $shortUrlStringifier,
) {
parent::__construct($visitsHelper);
}
protected function doConfigure(): void
{
$this
@ -24,4 +34,13 @@ class GetNonOrphanVisitsCommand extends AbstractVisitsListCommand
{
return $this->visitsHelper->nonOrphanVisits(new VisitsParams($dateRange));
}
/**
* @return array<string, string>
*/
protected function mapExtraFields(Visit $visit): array
{
$shortUrl = $visit->getShortUrl();
return $shortUrl === null ? [] : ['shortUrl' => $this->shortUrlStringifier->stringify($shortUrl)];
}
}

View file

@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\CLI\Command\Visit;
use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Model\VisitsParams;
use Symfony\Component\Console\Input\InputInterface;
@ -24,4 +25,12 @@ class GetOrphanVisitsCommand extends AbstractVisitsListCommand
{
return $this->visitsHelper->orphanVisits(new VisitsParams($dateRange));
}
/**
* @return array<string, string>
*/
protected function mapExtraFields(Visit $visit): array
{
return ['type' => $visit->type()->value];
}
}

View file

@ -8,6 +8,7 @@ use Cake\Chronos\Chronos;
use DateTimeInterface;
use Doctrine\ORM\Mapping\Builder\FieldBuilder;
use Jaybizzle\CrawlerDetect\CrawlerDetect;
use Laminas\Filter\Word\CamelCaseToSeparator;
use Laminas\InputFilter\InputFilter;
use PUGX\Shortid\Factory as ShortIdFactory;
use Shlinkio\Shlink\Common\Util\DateRange;
@ -19,6 +20,7 @@ use function print_r;
use function Shlinkio\Shlink\Common\buildDateRange;
use function sprintf;
use function str_repeat;
use function ucfirst;
function generateRandomShortCode(int $length): string
{
@ -115,3 +117,13 @@ function fieldWithUtf8Charset(FieldBuilder $field, array $emConfig, string $coll
default => $field,
};
}
function camelCaseToHumanFriendly(string $value): string
{
static $filter;
if ($filter === null) {
$filter = new CamelCaseToSeparator(' ');
}
return ucfirst($filter->filter($value));
}