From a6286c247a34522cb06e20a1ab5e46c1ca0522eb Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 29 Dec 2025 10:35:46 +0100 Subject: [PATCH] Allow visits to be generated in CSV format --- composer.json | 1 + .../src/Command/Visit/VisitsCommandUtils.php | 17 ++++++- .../ShortUrl/GetShortUrlVisitsCommandTest.php | 44 +++++++++++++------ 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index 6b560969..a205e391 100644 --- a/composer.json +++ b/composer.json @@ -33,6 +33,7 @@ "laminas/laminas-inputfilter": "^2.31", "laminas/laminas-servicemanager": "^3.23", "laminas/laminas-stdlib": "^3.20", + "league/csv": "^9.28", "matomo/matomo-php-tracker": "^3.3", "mezzio/mezzio": "^3.20", "mezzio/mezzio-fastroute": "^3.12", diff --git a/module/CLI/src/Command/Visit/VisitsCommandUtils.php b/module/CLI/src/Command/Visit/VisitsCommandUtils.php index 8089b02c..0eadc2c4 100644 --- a/module/CLI/src/Command/Visit/VisitsCommandUtils.php +++ b/module/CLI/src/Command/Visit/VisitsCommandUtils.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\Visit; +use League\Csv\Writer; use Shlinkio\Shlink\CLI\Input\VisitsListFormat; use Shlinkio\Shlink\CLI\Input\VisitsListInput; use Shlinkio\Shlink\CLI\Util\ShlinkTable; @@ -49,7 +50,21 @@ class VisitsCommandUtils Paginator $paginator, callable|null $mapExtraFields, ): void { - // TODO + $page = 1; + do { + $paginator->setCurrentPage($page); + + [$rows, $headers] = self::resolveRowsAndHeaders($paginator, $mapExtraFields); + $csv = Writer::fromString(); + if ($page === 1) { + $csv->insertOne($headers); + } + + $csv->insertAll($rows); + $output->write($csv->toString()); + + $page++; + } while ($paginator->hasNextPage()); } /** diff --git a/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php index 04a081c0..8e94b043 100644 --- a/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php @@ -6,10 +6,12 @@ namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl; use Cake\Chronos\Chronos; use Pagerfanta\Adapter\ArrayAdapter; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\CLI\Command\ShortUrl\GetShortUrlVisitsCommand; +use Shlinkio\Shlink\CLI\Input\VisitsListFormat; use Shlinkio\Shlink\Common\Paginator\Paginator; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; @@ -80,8 +82,11 @@ class GetShortUrlVisitsCommandTest extends TestCase ]); } - #[Test] - public function outputIsProperlyGenerated(): void + /** + * @param callable(Chronos $date): string $getExpectedOutput + */ + #[Test, DataProvider('provideOutput')] + public function outputIsProperlyGenerated(VisitsListFormat $format, callable $getExpectedOutput): void { $visit = Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::fromParams('bar', 'foo', ''))->locate( VisitLocation::fromLocation(new Location('', 'Spain', '', 'Madrid', 0, 0, '')), @@ -92,19 +97,32 @@ class GetShortUrlVisitsCommandTest extends TestCase $this->anything(), )->willReturn(new Paginator(new ArrayAdapter([$visit]))); - $this->commandTester->execute(['short-code' => $shortCode]); + $this->commandTester->execute(['short-code' => $shortCode, '--format' => $format->value]); $output = $this->commandTester->getDisplay(); - self::assertEquals( - <<date->toAtomString()} | bar | Spain | Madrid | - +---------+------------------ Page 1 of 1 ---------+---------+--------+ + self::assertEquals($getExpectedOutput($visit->date), $output); + } - OUTPUT, - $output, - ); + public static function provideOutput(): iterable + { + yield 'regular' => [ + VisitsListFormat::FULL, + static fn (Chronos $date) => <<toAtomString()} | bar | Spain | Madrid | + +---------+------------------ Page 1 of 1 ---------+---------+--------+ + + OUTPUT, + ]; + yield 'CSV' => [ + VisitsListFormat::CSV, + static fn (Chronos $date) => <<toAtomString()},bar,Spain,Madrid + + OUTPUT, + ]; } }