mirror of
https://github.com/shlinkio/shlink.git
synced 2026-02-28 04:03:12 +08:00
Extend and normalize output from visits console commands
This commit is contained in:
@@ -107,7 +107,7 @@ return [
|
||||
],
|
||||
Command\Visit\GetOrphanVisitsCommand::class => [Visit\VisitsStatsHelper::class],
|
||||
Command\Visit\DeleteOrphanVisitsCommand::class => [Visit\VisitsDeleter::class],
|
||||
Command\Visit\GetNonOrphanVisitsCommand::class => [Visit\VisitsStatsHelper::class, ShortUrlStringifier::class],
|
||||
Command\Visit\GetNonOrphanVisitsCommand::class => [Visit\VisitsStatsHelper::class],
|
||||
|
||||
Command\Api\GenerateKeyCommand::class => [ApiKeyService::class, ApiKey\RoleResolver::class],
|
||||
Command\Api\DisableKeyCommand::class => [ApiKeyService::class],
|
||||
@@ -119,11 +119,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, ShortUrlStringifier::class],
|
||||
Command\Tag\GetTagVisitsCommand::class => [Visit\VisitsStatsHelper::class],
|
||||
|
||||
Command\Domain\ListDomainsCommand::class => [DomainService::class],
|
||||
Command\Domain\DomainRedirectsCommand::class => [DomainService::class],
|
||||
Command\Domain\GetDomainVisitsCommand::class => [Visit\VisitsStatsHelper::class, ShortUrlStringifier::class],
|
||||
Command\Domain\GetDomainVisitsCommand::class => [Visit\VisitsStatsHelper::class],
|
||||
|
||||
Command\RedirectRule\ManageRedirectRulesCommand::class => [
|
||||
ShortUrl\ShortUrlResolver::class,
|
||||
|
||||
@@ -6,8 +6,6 @@ namespace Shlinkio\Shlink\CLI\Command\Domain;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Command\Visit\VisitsCommandUtils;
|
||||
use Shlinkio\Shlink\CLI\Input\VisitsListInput;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Symfony\Component\Console\Attribute\Argument;
|
||||
@@ -22,10 +20,8 @@ class GetDomainVisitsCommand extends Command
|
||||
{
|
||||
public const string NAME = 'domain:visits';
|
||||
|
||||
public function __construct(
|
||||
private readonly VisitsStatsHelperInterface $visitsHelper,
|
||||
private readonly ShortUrlStringifierInterface $shortUrlStringifier,
|
||||
) {
|
||||
public function __construct(private readonly VisitsStatsHelperInterface $visitsHelper)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@@ -36,17 +32,8 @@ class GetDomainVisitsCommand extends Command
|
||||
#[MapInput] VisitsListInput $input,
|
||||
): int {
|
||||
$paginator = $this->visitsHelper->visitsForDomain($domain, new VisitsParams($input->dateRange()));
|
||||
VisitsCommandUtils::renderOutput($io, $input, $paginator, $this->mapExtraFields(...));
|
||||
VisitsCommandUtils::renderOutput($io, $input, $paginator);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function mapExtraFields(Visit $visit): array
|
||||
{
|
||||
$shortUrl = $visit->shortUrl;
|
||||
return $shortUrl === null ? [] : ['shortUrl' => $this->shortUrlStringifier->stringify($shortUrl)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ namespace Shlinkio\Shlink\CLI\Command\Tag;
|
||||
use Shlinkio\Shlink\CLI\Command\Visit\VisitsCommandUtils;
|
||||
use Shlinkio\Shlink\CLI\Input\VisitsListInput;
|
||||
use Shlinkio\Shlink\Core\Domain\Entity\Domain;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\WithDomainVisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Symfony\Component\Console\Attribute\Argument;
|
||||
@@ -24,10 +22,8 @@ class GetTagVisitsCommand extends Command
|
||||
{
|
||||
public const string NAME = 'tag:visits';
|
||||
|
||||
public function __construct(
|
||||
private readonly VisitsStatsHelperInterface $visitsHelper,
|
||||
private readonly ShortUrlStringifierInterface $shortUrlStringifier,
|
||||
) {
|
||||
public function __construct(private readonly VisitsStatsHelperInterface $visitsHelper)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@@ -47,17 +43,8 @@ class GetTagVisitsCommand extends Command
|
||||
domain: $domain,
|
||||
));
|
||||
|
||||
VisitsCommandUtils::renderOutput($io, $input, $paginator, $this->mapExtraFields(...));
|
||||
VisitsCommandUtils::renderOutput($io, $input, $paginator);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function mapExtraFields(Visit $visit): array
|
||||
{
|
||||
$shortUrl = $visit->shortUrl;
|
||||
return $shortUrl === null ? [] : ['shortUrl' => $this->shortUrlStringifier->stringify($shortUrl)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ namespace Shlinkio\Shlink\CLI\Command\Visit;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Input\VisitsListInput;
|
||||
use Shlinkio\Shlink\Core\Domain\Entity\Domain;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\WithDomainVisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
@@ -21,10 +19,8 @@ class GetNonOrphanVisitsCommand extends Command
|
||||
{
|
||||
public const string NAME = 'visit:non-orphan';
|
||||
|
||||
public function __construct(
|
||||
private readonly VisitsStatsHelperInterface $visitsHelper,
|
||||
private readonly ShortUrlStringifierInterface $shortUrlStringifier,
|
||||
) {
|
||||
public function __construct(private readonly VisitsStatsHelperInterface $visitsHelper)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@@ -42,17 +38,8 @@ class GetNonOrphanVisitsCommand extends Command
|
||||
dateRange: $input->dateRange(),
|
||||
domain: $domain,
|
||||
));
|
||||
VisitsCommandUtils::renderOutput($io, $input, $paginator, $this->mapExtraFields(...));
|
||||
VisitsCommandUtils::renderOutput($io, $input, $paginator);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function mapExtraFields(Visit $visit): array
|
||||
{
|
||||
$shortUrl = $visit->shortUrl;
|
||||
return $shortUrl === null ? [] : ['shortUrl' => $this->shortUrlStringifier->stringify($shortUrl)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace Shlinkio\Shlink\CLI\Command\Visit;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Input\VisitsListInput;
|
||||
use Shlinkio\Shlink\Core\Domain\Entity\Domain;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitType;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
@@ -42,16 +41,8 @@ class GetOrphanVisitsCommand extends Command
|
||||
domain: $domain,
|
||||
type: $type,
|
||||
));
|
||||
VisitsCommandUtils::renderOutput($io, $input, $paginator, $this->mapExtraFields(...));
|
||||
VisitsCommandUtils::renderOutput($io, $input, $paginator);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function mapExtraFields(Visit $visit): array
|
||||
{
|
||||
return ['type' => $visit->type->value];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,16 +13,12 @@ use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtils;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function Shlinkio\Shlink\Core\ArrayUtils\select_keys;
|
||||
use function Shlinkio\Shlink\Core\camelCaseToHumanFriendly;
|
||||
|
||||
class VisitsCommandUtils
|
||||
{
|
||||
/**
|
||||
* @param Paginator<Visit> $paginator
|
||||
* @param null|callable(Visit $visits): array<string, string> $mapExtraFields
|
||||
*/
|
||||
public static function renderOutput(
|
||||
OutputInterface $output,
|
||||
@@ -36,25 +32,21 @@ class VisitsCommandUtils
|
||||
}
|
||||
|
||||
match ($inputData->format) {
|
||||
VisitsListFormat::CSV => self::renderCSVOutput($output, $paginator, $mapExtraFields),
|
||||
default => self::renderHumanFriendlyOutput($output, $paginator, $mapExtraFields),
|
||||
VisitsListFormat::CSV => self::renderCSVOutput($output, $paginator),
|
||||
default => self::renderHumanFriendlyOutput($output, $paginator),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Paginator<Visit> $paginator
|
||||
* @param null|callable(Visit $visits): array<string, string> $mapExtraFields
|
||||
*/
|
||||
private static function renderCSVOutput(
|
||||
OutputInterface $output,
|
||||
Paginator $paginator,
|
||||
callable|null $mapExtraFields,
|
||||
): void {
|
||||
private static function renderCSVOutput(OutputInterface $output, Paginator $paginator): void
|
||||
{
|
||||
$page = 1;
|
||||
do {
|
||||
$paginator->setCurrentPage($page);
|
||||
|
||||
[$rows, $headers] = self::resolveRowsAndHeaders($paginator, $mapExtraFields);
|
||||
[$rows, $headers] = self::resolveRowsAndHeaders($paginator);
|
||||
$csv = Writer::fromString();
|
||||
if ($page === 1) {
|
||||
$csv->insertOne($headers);
|
||||
@@ -69,19 +61,15 @@ class VisitsCommandUtils
|
||||
|
||||
/**
|
||||
* @param Paginator<Visit> $paginator
|
||||
* @param null|callable(Visit $visits): array<string, string> $mapExtraFields
|
||||
*/
|
||||
private static function renderHumanFriendlyOutput(
|
||||
OutputInterface $output,
|
||||
Paginator $paginator,
|
||||
callable|null $mapExtraFields,
|
||||
): void {
|
||||
private static function renderHumanFriendlyOutput(OutputInterface $output, Paginator $paginator): void
|
||||
{
|
||||
$page = 1;
|
||||
do {
|
||||
$paginator->setCurrentPage($page);
|
||||
$page++;
|
||||
|
||||
[$rows, $headers] = self::resolveRowsAndHeaders($paginator, $mapExtraFields);
|
||||
[$rows, $headers] = self::resolveRowsAndHeaders($paginator);
|
||||
ShlinkTable::default($output)->render(
|
||||
$headers,
|
||||
$rows,
|
||||
@@ -92,35 +80,38 @@ class VisitsCommandUtils
|
||||
|
||||
/**
|
||||
* @param Paginator<Visit> $paginator
|
||||
* @param null|callable(Visit $visits): array<string, string> $mapExtraFields
|
||||
*/
|
||||
private static function resolveRowsAndHeaders(Paginator $paginator, callable|null $mapExtraFields): array
|
||||
private static function resolveRowsAndHeaders(Paginator $paginator): array
|
||||
{
|
||||
$extraKeys = null;
|
||||
$mapExtraFields ??= static fn (Visit $_) => [];
|
||||
|
||||
$rows = array_map(function (Visit $visit) use (&$extraKeys, $mapExtraFields) {
|
||||
$extraFields = $mapExtraFields($visit);
|
||||
$extraKeys ??= array_keys($extraFields);
|
||||
|
||||
$rowData = [
|
||||
'referer' => $visit->referer,
|
||||
'date' => $visit->date->toAtomString(),
|
||||
'userAgent' => $visit->userAgent,
|
||||
'potentialBot' => $visit->potentialBot,
|
||||
'country' => $visit->getVisitLocation()->countryName ?? 'Unknown',
|
||||
'city' => $visit->getVisitLocation()->cityName ?? 'Unknown',
|
||||
...$extraFields,
|
||||
];
|
||||
|
||||
// Filter out unknown keys
|
||||
return select_keys($rowData, ['referer', 'date', 'userAgent', 'country', 'city', ...$extraKeys]);
|
||||
}, [...$paginator->getCurrentPageResults()]);
|
||||
$extra = array_map(camelCaseToHumanFriendly(...), $extraKeys ?? []);
|
||||
|
||||
return [
|
||||
$rows,
|
||||
['Referer', 'Date', 'User agent', 'Country', 'City', ...$extra],
|
||||
$headers = [
|
||||
'Date',
|
||||
'Potential bot',
|
||||
'User agent',
|
||||
'Referer',
|
||||
'Country',
|
||||
'Region',
|
||||
'City',
|
||||
'Visited URL',
|
||||
'Redirect URL',
|
||||
'Type',
|
||||
];
|
||||
$rows = array_map(function (Visit $visit) {
|
||||
$visitLocation = $visit->visitLocation;
|
||||
|
||||
return [
|
||||
'date' => $visit->date->toAtomString(),
|
||||
'potentialBot' => $visit->potentialBot ? 'Potential bot' : '',
|
||||
'userAgent' => $visit->userAgent,
|
||||
'referer' => $visit->referer,
|
||||
'country' => $visitLocation->countryName ?? 'Unknown',
|
||||
'region' => $visitLocation->regionName ?? 'Unknown',
|
||||
'city' => $visitLocation->cityName ?? 'Unknown',
|
||||
'visitedUrl' => $visit->visitedUrl ?? 'Unknown',
|
||||
'redirectUrl' => $visit->redirectUrl ?? 'Unknown',
|
||||
'type' => $visit->type->value,
|
||||
];
|
||||
}, [...$paginator->getCurrentPageResults()]);
|
||||
|
||||
return [$rows, $headers];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\CLI\Command\Domain\GetDomainVisitsCommand;
|
||||
use Shlinkio\Shlink\Common\Paginator\Paginator;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use ShlinkioTest\Shlink\CLI\Util\CliTestUtils;
|
||||
@@ -24,16 +24,11 @@ class GetDomainVisitsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & VisitsStatsHelperInterface $visitsHelper;
|
||||
private MockObject & ShortUrlStringifierInterface $stringifier;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class);
|
||||
$this->stringifier = $this->createMock(ShortUrlStringifierInterface::class);
|
||||
|
||||
$this->commandTester = CliTestUtils::testerForCommand(
|
||||
new GetDomainVisitsCommand($this->visitsHelper, $this->stringifier),
|
||||
);
|
||||
$this->commandTester = CliTestUtils::testerForCommand(new GetDomainVisitsCommand($this->visitsHelper));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -48,22 +43,22 @@ class GetDomainVisitsCommandTest extends TestCase
|
||||
$domain,
|
||||
$this->anything(),
|
||||
)->willReturn(new Paginator(new ArrayAdapter([$visit])));
|
||||
$this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn(
|
||||
'the_short_url',
|
||||
);
|
||||
|
||||
$this->commandTester->execute(['domain' => $domain]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$type = VisitType::VALID_SHORT_URL->value;
|
||||
|
||||
self::assertEquals(
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
<<<OUTPUT
|
||||
+---------+---------------------------+------------+---------+--------+---------------+
|
||||
| Referer | Date | User agent | Country | City | Short Url |
|
||||
+---------+---------------------------+------------+---------+--------+---------------+
|
||||
| foo | {$visit->date->toAtomString()} | bar | Spain | Madrid | the_short_url |
|
||||
+---------+-------------------------- Page 1 of 1 -+---------+--------+---------------+
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
|
||||
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
|
||||
|
||||
OUTPUT,
|
||||
// phpcs:enable
|
||||
$output,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,20 +107,22 @@ class GetShortUrlVisitsCommandTest extends TestCase
|
||||
{
|
||||
yield 'regular' => [
|
||||
VisitsListFormat::FULL,
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
static fn (Chronos $date) => <<<OUTPUT
|
||||
+---------+---------------------------+------------+---------+--------+
|
||||
| Referer | Date | User agent | Country | City |
|
||||
+---------+---------------------------+------------+---------+--------+
|
||||
| foo | {$date->toAtomString()} | bar | Spain | Madrid |
|
||||
+---------+------------------ Page 1 of 1 ---------+---------+--------+
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| {$date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | valid_short_url |
|
||||
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
|
||||
|
||||
OUTPUT,
|
||||
// phpcs:enable
|
||||
];
|
||||
yield 'CSV' => [
|
||||
VisitsListFormat::CSV,
|
||||
static fn (Chronos $date) => <<<OUTPUT
|
||||
Referer,Date,"User agent",Country,City
|
||||
foo,{$date->toAtomString()},bar,Spain,Madrid
|
||||
Date,"Potential bot","User agent",Referer,Country,Region,City,"Visited URL","Redirect URL",Type
|
||||
{$date->toAtomString()},,bar,foo,Spain,,Madrid,,Unknown,valid_short_url
|
||||
|
||||
OUTPUT,
|
||||
];
|
||||
|
||||
@@ -11,10 +11,10 @@ use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\CLI\Command\Tag\GetTagVisitsCommand;
|
||||
use Shlinkio\Shlink\Common\Paginator\Paginator;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use ShlinkioTest\Shlink\CLI\Util\CliTestUtils;
|
||||
@@ -24,16 +24,11 @@ class GetTagVisitsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & VisitsStatsHelperInterface $visitsHelper;
|
||||
private MockObject & ShortUrlStringifierInterface $stringifier;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class);
|
||||
$this->stringifier = $this->createMock(ShortUrlStringifierInterface::class);
|
||||
|
||||
$this->commandTester = CliTestUtils::testerForCommand(
|
||||
new GetTagVisitsCommand($this->visitsHelper, $this->stringifier),
|
||||
);
|
||||
$this->commandTester = CliTestUtils::testerForCommand(new GetTagVisitsCommand($this->visitsHelper));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -47,20 +42,22 @@ class GetTagVisitsCommandTest extends TestCase
|
||||
$this->visitsHelper->expects($this->once())->method('visitsForTag')->with($tag, $this->anything())->willReturn(
|
||||
new Paginator(new ArrayAdapter([$visit])),
|
||||
);
|
||||
$this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn('the_short_url');
|
||||
|
||||
$this->commandTester->execute(['tag' => $tag]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$type = VisitType::VALID_SHORT_URL->value;
|
||||
|
||||
self::assertEquals(
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
<<<OUTPUT
|
||||
+---------+---------------------------+------------+---------+--------+---------------+
|
||||
| Referer | Date | User agent | Country | City | Short Url |
|
||||
+---------+---------------------------+------------+---------+--------+---------------+
|
||||
| foo | {$visit->date->toAtomString()} | bar | Spain | Madrid | the_short_url |
|
||||
+---------+-------------------------- Page 1 of 1 -+---------+--------+---------------+
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
|
||||
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
|
||||
|
||||
OUTPUT,
|
||||
// phpcs:enable
|
||||
$output,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\CLI\Command\Visit\GetNonOrphanVisitsCommand;
|
||||
use Shlinkio\Shlink\Common\Paginator\Paginator;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
use ShlinkioTest\Shlink\CLI\Util\CliTestUtils;
|
||||
@@ -24,16 +24,11 @@ class GetNonOrphanVisitsCommandTest extends TestCase
|
||||
{
|
||||
private CommandTester $commandTester;
|
||||
private MockObject & VisitsStatsHelperInterface $visitsHelper;
|
||||
private MockObject & ShortUrlStringifierInterface $stringifier;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class);
|
||||
$this->stringifier = $this->createMock(ShortUrlStringifierInterface::class);
|
||||
|
||||
$this->commandTester = CliTestUtils::testerForCommand(
|
||||
new GetNonOrphanVisitsCommand($this->visitsHelper, $this->stringifier),
|
||||
);
|
||||
$this->commandTester = CliTestUtils::testerForCommand(new GetNonOrphanVisitsCommand($this->visitsHelper));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -46,20 +41,22 @@ class GetNonOrphanVisitsCommandTest extends TestCase
|
||||
$this->visitsHelper->expects($this->once())->method('nonOrphanVisits')->withAnyParameters()->willReturn(
|
||||
new Paginator(new ArrayAdapter([$visit])),
|
||||
);
|
||||
$this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn('the_short_url');
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$type = VisitType::VALID_SHORT_URL->value;
|
||||
|
||||
self::assertEquals(
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
<<<OUTPUT
|
||||
+---------+---------------------------+------------+---------+--------+---------------+
|
||||
| Referer | Date | User agent | Country | City | Short Url |
|
||||
+---------+---------------------------+------------+---------+--------+---------------+
|
||||
| foo | {$visit->date->toAtomString()} | bar | Spain | Madrid | the_short_url |
|
||||
+---------+-------------------------- Page 1 of 1 -+---------+--------+---------------+
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
|
||||
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
|
||||
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
|
||||
|
||||
OUTPUT,
|
||||
// phpcs:enable
|
||||
$output,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,16 +48,19 @@ class GetOrphanVisitsCommandTest extends TestCase
|
||||
|
||||
$this->commandTester->execute($args);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$type = OrphanVisitType::BASE_URL->value;
|
||||
|
||||
self::assertEquals(
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
<<<OUTPUT
|
||||
+---------+---------------------------+------------+---------+--------+----------+
|
||||
| Referer | Date | User agent | Country | City | Type |
|
||||
+---------+---------------------------+------------+---------+--------+----------+
|
||||
| foo | {$visit->date->toAtomString()} | bar | Spain | Madrid | base_url |
|
||||
+---------+----------------------- Page 1 of 1 ----+---------+--------+----------+
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+----------+
|
||||
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
|
||||
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+----------+
|
||||
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
|
||||
+---------------------------+---------------+------------+--- Page 1 of 1 ---+--------+--------+-------------+--------------+----------+
|
||||
|
||||
OUTPUT,
|
||||
// phpcs:enable
|
||||
$output,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,12 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\ArrayUtils;
|
||||
|
||||
use function array_filter;
|
||||
use function array_reduce;
|
||||
use function in_array;
|
||||
|
||||
use const ARRAY_FILTER_USE_KEY;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param T $value
|
||||
@@ -20,18 +16,6 @@ function contains(mixed $value, array $array): bool
|
||||
return in_array($value, $array, strict: true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array[] $multiArray
|
||||
*/
|
||||
function flatten(array $multiArray): array
|
||||
{
|
||||
return array_reduce(
|
||||
$multiArray,
|
||||
static fn (array $carry, array $value) => [...$carry, ...$value],
|
||||
initial: [],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a callback returns true for at least one item in a collection.
|
||||
* @param callable(mixed $value, mixed $key): bool $callback
|
||||
@@ -62,21 +46,6 @@ function every(iterable $collection, callable $callback): bool
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing only those entries in the array whose key is in the supplied keys.
|
||||
*/
|
||||
function select_keys(array $array, array $keys): array
|
||||
{
|
||||
return array_filter(
|
||||
$array,
|
||||
static fn (string $key) => contains(
|
||||
$key,
|
||||
$keys,
|
||||
),
|
||||
ARRAY_FILTER_USE_KEY,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @template R
|
||||
|
||||
@@ -58,7 +58,7 @@ readonly class MatomoVisitSender implements MatomoVisitSenderInterface
|
||||
->setUrlReferrer($visit->referer)
|
||||
->setForceVisitDateTime($visit->date->setTimezone('UTC')->toDateTimeString());
|
||||
|
||||
$location = $visit->getVisitLocation();
|
||||
$location = $visit->visitLocation;
|
||||
if ($location !== null) {
|
||||
$tracker
|
||||
->setCity($location->cityName)
|
||||
|
||||
@@ -29,7 +29,7 @@ class Visit extends AbstractEntity implements JsonSerializable
|
||||
public readonly string|null $remoteAddr = null,
|
||||
public readonly string|null $visitedUrl = null,
|
||||
public readonly string|null $redirectUrl = null,
|
||||
private VisitLocation|null $visitLocation = null,
|
||||
private(set) VisitLocation|null $visitLocation = null,
|
||||
public readonly Chronos $date = new Chronos(),
|
||||
) {
|
||||
}
|
||||
@@ -124,11 +124,6 @@ class Visit extends AbstractEntity implements JsonSerializable
|
||||
return ! empty($this->remoteAddr);
|
||||
}
|
||||
|
||||
public function getVisitLocation(): VisitLocation|null
|
||||
{
|
||||
return $this->visitLocation;
|
||||
}
|
||||
|
||||
public function locate(VisitLocation $visitLocation): self
|
||||
{
|
||||
$this->visitLocation = $visitLocation;
|
||||
|
||||
@@ -72,7 +72,7 @@ readonly class VisitLocator implements VisitLocatorInterface
|
||||
|
||||
private function locateVisit(Visit $visit, VisitLocation $location, VisitGeolocationHelperInterface $helper): void
|
||||
{
|
||||
$prevLocation = $visit->getVisitLocation();
|
||||
$prevLocation = $visit->visitLocation;
|
||||
|
||||
$visit->locate($location);
|
||||
$this->em->persist($visit);
|
||||
|
||||
Reference in New Issue
Block a user