mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 04:00:57 +03:00
Added logic to resolve extra columns on visits commands
This commit is contained in:
parent
00002b1e24
commit
353ac0fc0c
8 changed files with 123 additions and 10 deletions
|
@ -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,
|
||||
|
|
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue