From 5d0f306bccffcbeab3d4d9da8015539eaab21620 Mon Sep 17 00:00:00 2001
From: KetchupBomb <king.me.brad@gmail.com>
Date: Sat, 10 Apr 2021 06:50:34 +0000
Subject: [PATCH 1/5] Feature/show API key info in short-url CLI

---
 .../Command/ShortUrl/ListShortUrlsCommand.php | 105 +++++++++++-------
 module/Core/src/Entity/ShortUrl.php           |   5 +
 2 files changed, 72 insertions(+), 38 deletions(-)

diff --git a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
index 24689bcb..aa975a40 100644
--- a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
+++ b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
@@ -4,12 +4,15 @@ declare(strict_types=1);
 
 namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
 
+use closure;
 use Shlinkio\Shlink\CLI\Command\Util\AbstractWithDateRangeCommand;
 use Shlinkio\Shlink\CLI\Util\ExitCodes;
 use Shlinkio\Shlink\CLI\Util\ShlinkTable;
 use Shlinkio\Shlink\Common\Paginator\Paginator;
 use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtilsTrait;
 use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
+use Shlinkio\Shlink\Core\Entity\ShortUrl;
+use Shlinkio\Shlink\Core\Entity\Tag;
 use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
 use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
 use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
@@ -19,10 +22,13 @@ use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\Console\Style\SymfonyStyle;
 
+use function array_keys;
 use function array_pad;
+use function count;
 use function explode;
 use function Functional\map;
-use function implode;
+use function is_null;
+use function join;
 use function sprintf;
 
 class ListShortUrlsCommand extends AbstractWithDateRangeCommand
@@ -30,18 +36,6 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
     use PagerfantaUtilsTrait;
 
     public const NAME = 'short-url:list';
-    private const COLUMNS_TO_SHOW = [
-        'shortCode',
-        'title',
-        'shortUrl',
-        'longUrl',
-        'dateCreated',
-        'visitsCount',
-    ];
-    private const COLUMNS_TO_SHOW_WITH_TAGS = [
-        ...self::COLUMNS_TO_SHOW,
-        'tags',
-    ];
 
     private ShortUrlServiceInterface $shortUrlService;
     private DataTransformerInterface $transformer;
@@ -90,6 +84,18 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
                 InputOption::VALUE_NONE,
                 'Whether to display the tags or not.',
             )
+            ->addOption(
+                'show-api-key',
+                'k',
+                InputOption::VALUE_NONE,
+                'Whether to display the API key from which the URL was generated or not.',
+            )
+            ->addOption(
+                'show-api-key-name',
+                'm',
+                InputOption::VALUE_NONE,
+                'Whether to display the API key name from which the URL was generated or not.',
+            )
             ->addOption(
                 'all',
                 'a',
@@ -117,12 +123,38 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
         $searchTerm = $this->getOptionWithDeprecatedFallback($input, 'search-term');
         $tags = $input->getOption('tags');
         $tags = ! empty($tags) ? explode(',', $tags) : [];
-        $showTags = $this->getOptionWithDeprecatedFallback($input, 'show-tags');
         $all = $input->getOption('all');
         $startDate = $this->getStartDateOption($input, $output);
         $endDate = $this->getEndDateOption($input, $output);
         $orderBy = $this->processOrderBy($input);
 
+
+        $transformerLookup = fn (string $key): closure
+        => fn (ShortUrl $shortUrl)
+            => $this->transformer->transform($shortUrl)[$key];
+
+        $columnMap = [
+            'Short Code'    => $transformerLookup('shortCode'),
+            'Title'         => $transformerLookup('title'),
+            'Short URL'     => $transformerLookup('shortUrl'),
+            'Long URL'      => $transformerLookup('longUrl'),
+            'Date created'  => $transformerLookup('dateCreated'),
+            'Visits count'  => $transformerLookup('visitsCount'),
+        ];
+        if ($this->getOptionWithDeprecatedFallback($input, 'show-tags')) {
+            $columnMap['Tags'] = fn (ShortUrl $shortUrl): string
+                => join(', ', map($shortUrl->getTags(), fn (Tag $tag): string => (string) $tag));
+        }
+        if ($input->getOption('show-api-key')) {
+            $columnMap['API Key'] = fn (ShortUrl $shortUrl): string => (string) $shortUrl->authorApiKey();
+        }
+        if ($input->getOption('show-api-key-name')) {
+            $columnMap['API Key Name'] = fn (ShortUrl $shortUrl): ?string => ! is_null($shortUrl->authorApiKey())
+                ? $shortUrl->authorApiKey()->name()
+                : null;
+        }
+
+
         $data = [
             ShortUrlsParamsInputFilter::SEARCH_TERM => $searchTerm,
             ShortUrlsParamsInputFilter::TAGS => $tags,
@@ -137,7 +169,7 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
 
         do {
             $data[ShortUrlsParamsInputFilter::PAGE] = $page;
-            $result = $this->renderPage($output, $showTags, ShortUrlsParams::fromRawData($data), $all);
+            $result = $this->renderPage($output, $columnMap, ShortUrlsParams::fromRawData($data), $all);
             $page++;
 
             $continue = $result->hasNextPage() && $io->confirm(
@@ -152,32 +184,29 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
         return ExitCodes::EXIT_SUCCESS;
     }
 
-    private function renderPage(OutputInterface $output, bool $showTags, ShortUrlsParams $params, bool $all): Paginator
-    {
-        $result = $this->shortUrlService->listShortUrls($params);
+    private function renderPage(
+        OutputInterface $output,
+        array $columnMap,
+        ShortUrlsParams $params,
+        bool $all
+    ): Paginator {
+        $shortUrls = $this->shortUrlService->listShortUrls($params);
 
-        $headers = ['Short code', 'Title', 'Short URL', 'Long URL', 'Date created', 'Visits count'];
-        if ($showTags) {
-            $headers[] = 'Tags';
-        }
+        $rows = map($shortUrls, fn (ShortUrl $shortUrl)
+            => map($columnMap, fn (callable $call)
+                => $call($shortUrl)));
 
-        $rows = [];
-        foreach ($result as $row) {
-            $columnsToShow = $showTags ? self::COLUMNS_TO_SHOW_WITH_TAGS : self::COLUMNS_TO_SHOW;
-            $shortUrl = $this->transformer->transform($row);
-            if ($showTags) {
-                $shortUrl['tags'] = implode(', ', $shortUrl['tags']);
-            }
+        ShlinkTable::fromOutput($output)
+            ->render(
+                array_keys($columnMap),
+                $rows,
+                $all ? null : $this->formatCurrentPageMessage(
+                    $shortUrls,
+                    'Page %s of %s',
+                ),
+            );
 
-            $rows[] = map($columnsToShow, fn (string $prop) => $shortUrl[$prop]);
-        }
-
-        ShlinkTable::fromOutput($output)->render($headers, $rows, $all ? null : $this->formatCurrentPageMessage(
-            $result,
-            'Page %s of %s',
-        ));
-
-        return $result;
+        return $shortUrls;
     }
 
     private function processOrderBy(InputInterface $input): ?string
diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php
index 810281fa..84f215de 100644
--- a/module/Core/src/Entity/ShortUrl.php
+++ b/module/Core/src/Entity/ShortUrl.php
@@ -132,6 +132,11 @@ class ShortUrl extends AbstractEntity
         return $this->tags;
     }
 
+    public function authorApiKey(): ?ApiKey
+    {
+        return $this->authorApiKey;
+    }
+
     public function getValidSince(): ?Chronos
     {
         return $this->validSince;

From a896fbbb9081b1ffa89f1568698f476eed306d3c Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Sun, 11 Apr 2021 10:50:35 +0200
Subject: [PATCH 2/5] Fixed coding styles

---
 .../Command/ShortUrl/ListShortUrlsCommand.php | 58 ++++++++-----------
 1 file changed, 25 insertions(+), 33 deletions(-)

diff --git a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
index aa975a40..69fc2556 100644
--- a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
+++ b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
@@ -4,7 +4,6 @@ declare(strict_types=1);
 
 namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
 
-use closure;
 use Shlinkio\Shlink\CLI\Command\Util\AbstractWithDateRangeCommand;
 use Shlinkio\Shlink\CLI\Util\ExitCodes;
 use Shlinkio\Shlink\CLI\Util\ShlinkTable;
@@ -24,11 +23,9 @@ use Symfony\Component\Console\Style\SymfonyStyle;
 
 use function array_keys;
 use function array_pad;
-use function count;
 use function explode;
 use function Functional\map;
-use function is_null;
-use function join;
+use function implode;
 use function sprintf;
 
 class ListShortUrlsCommand extends AbstractWithDateRangeCommand
@@ -128,32 +125,33 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
         $endDate = $this->getEndDateOption($input, $output);
         $orderBy = $this->processOrderBy($input);
 
-
-        $transformerLookup = fn (string $key): closure
-        => fn (ShortUrl $shortUrl)
-            => $this->transformer->transform($shortUrl)[$key];
+        $transformerLookup = fn (string $key): callable =>
+            fn (ShortUrl $shortUrl) => $this->transformer->transform($shortUrl)[$key];
 
         $columnMap = [
-            'Short Code'    => $transformerLookup('shortCode'),
-            'Title'         => $transformerLookup('title'),
-            'Short URL'     => $transformerLookup('shortUrl'),
-            'Long URL'      => $transformerLookup('longUrl'),
-            'Date created'  => $transformerLookup('dateCreated'),
-            'Visits count'  => $transformerLookup('visitsCount'),
+            'Short Code' => $transformerLookup('shortCode'),
+            'Title' => $transformerLookup('title'),
+            'Short URL' => $transformerLookup('shortUrl'),
+            'Long URL' => $transformerLookup('longUrl'),
+            'Date created' => $transformerLookup('dateCreated'),
+            'Visits count' => $transformerLookup('visitsCount'),
         ];
         if ($this->getOptionWithDeprecatedFallback($input, 'show-tags')) {
-            $columnMap['Tags'] = fn (ShortUrl $shortUrl): string
-                => join(', ', map($shortUrl->getTags(), fn (Tag $tag): string => (string) $tag));
+            $columnMap['Tags'] = static fn (ShortUrl $shortUrl): string => implode(
+                ', ',
+                map($shortUrl->getTags(), fn (Tag $tag): string => (string) $tag),
+            );
         }
         if ($input->getOption('show-api-key')) {
-            $columnMap['API Key'] = fn (ShortUrl $shortUrl): string => (string) $shortUrl->authorApiKey();
+            $columnMap['API Key'] = static fn (ShortUrl $shortUrl): string => (string) $shortUrl->authorApiKey();
         }
         if ($input->getOption('show-api-key-name')) {
-            $columnMap['API Key Name'] = fn (ShortUrl $shortUrl): ?string => ! is_null($shortUrl->authorApiKey())
-                ? $shortUrl->authorApiKey()->name()
-                : null;
-        }
+            $columnMap['API Key Name'] = static function (ShortUrl $shortUrl): ?string {
+                $apiKey = $shortUrl->authorApiKey();
 
+                return $apiKey !== null ? $apiKey->name() : null;
+            };
+        }
 
         $data = [
             ShortUrlsParamsInputFilter::SEARCH_TERM => $searchTerm,
@@ -192,19 +190,13 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
     ): Paginator {
         $shortUrls = $this->shortUrlService->listShortUrls($params);
 
-        $rows = map($shortUrls, fn (ShortUrl $shortUrl)
-            => map($columnMap, fn (callable $call)
-                => $call($shortUrl)));
+        $rows = map($shortUrls, fn (ShortUrl $shortUrl) => map($columnMap, fn (callable $call) => $call($shortUrl)));
 
-        ShlinkTable::fromOutput($output)
-            ->render(
-                array_keys($columnMap),
-                $rows,
-                $all ? null : $this->formatCurrentPageMessage(
-                    $shortUrls,
-                    'Page %s of %s',
-                ),
-            );
+        ShlinkTable::fromOutput($output)->render(
+            array_keys($columnMap),
+            $rows,
+            $all ? null : $this->formatCurrentPageMessage($shortUrls, 'Page %s of %s'),
+        );
 
         return $shortUrls;
     }

From 5ddac7866b4ce1e673e1b142a59833fd55aa10e7 Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Sun, 11 Apr 2021 11:06:29 +0200
Subject: [PATCH 3/5] Ensured short URL transformation happens only once per
 short URL when listing from CLI

---
 .../Command/ShortUrl/ListShortUrlsCommand.php | 32 +++++++++----------
 1 file changed, 15 insertions(+), 17 deletions(-)

diff --git a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
index 69fc2556..8aa6bb1d 100644
--- a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
+++ b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
@@ -11,7 +11,6 @@ use Shlinkio\Shlink\Common\Paginator\Paginator;
 use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtilsTrait;
 use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
 use Shlinkio\Shlink\Core\Entity\ShortUrl;
-use Shlinkio\Shlink\Core\Entity\Tag;
 use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
 use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
 use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
@@ -125,28 +124,24 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
         $endDate = $this->getEndDateOption($input, $output);
         $orderBy = $this->processOrderBy($input);
 
-        $transformerLookup = fn (string $key): callable =>
-            fn (ShortUrl $shortUrl) => $this->transformer->transform($shortUrl)[$key];
-
+        $pickProp = static fn (string $prop): callable => static fn (array $shortUrl) => $shortUrl[$prop];
         $columnMap = [
-            'Short Code' => $transformerLookup('shortCode'),
-            'Title' => $transformerLookup('title'),
-            'Short URL' => $transformerLookup('shortUrl'),
-            'Long URL' => $transformerLookup('longUrl'),
-            'Date created' => $transformerLookup('dateCreated'),
-            'Visits count' => $transformerLookup('visitsCount'),
+            'Short Code' => $pickProp('shortCode'),
+            'Title' => $pickProp('title'),
+            'Short URL' => $pickProp('shortUrl'),
+            'Long URL' => $pickProp('longUrl'),
+            'Date created' => $pickProp('dateCreated'),
+            'Visits count' => $pickProp('visitsCount'),
         ];
         if ($this->getOptionWithDeprecatedFallback($input, 'show-tags')) {
-            $columnMap['Tags'] = static fn (ShortUrl $shortUrl): string => implode(
-                ', ',
-                map($shortUrl->getTags(), fn (Tag $tag): string => (string) $tag),
-            );
+            $columnMap['Tags'] = static fn (array $shortUrl): string => implode(', ', $shortUrl['tags']);
         }
         if ($input->getOption('show-api-key')) {
-            $columnMap['API Key'] = static fn (ShortUrl $shortUrl): string => (string) $shortUrl->authorApiKey();
+            $columnMap['API Key'] = static fn (array $_, ShortUrl $shortUrl): string =>
+                (string) $shortUrl->authorApiKey();
         }
         if ($input->getOption('show-api-key-name')) {
-            $columnMap['API Key Name'] = static function (ShortUrl $shortUrl): ?string {
+            $columnMap['API Key Name'] = static function (array $_, ShortUrl $shortUrl): ?string {
                 $apiKey = $shortUrl->authorApiKey();
 
                 return $apiKey !== null ? $apiKey->name() : null;
@@ -190,7 +185,10 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
     ): Paginator {
         $shortUrls = $this->shortUrlService->listShortUrls($params);
 
-        $rows = map($shortUrls, fn (ShortUrl $shortUrl) => map($columnMap, fn (callable $call) => $call($shortUrl)));
+        $rows = map($shortUrls, function (ShortUrl $shortUrl) use ($columnMap) {
+            $rawShortUrl = $this->transformer->transform($shortUrl);
+            return map($columnMap, fn (callable $call) => $call($rawShortUrl, $shortUrl));
+        });
 
         ShlinkTable::fromOutput($output)->render(
             array_keys($columnMap),

From 334d95c843de9c70e84512e12234e5312acd93ef Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Sun, 11 Apr 2021 11:29:42 +0200
Subject: [PATCH 4/5] Improved test covering ListSHortUrlsCommand with optional
 tags

---
 .../Command/ShortUrl/ListShortUrlsCommand.php | 64 +++++++++-------
 .../ShortUrl/ListShortUrlsCommandTest.php     | 76 +++++++++++++++++--
 2 files changed, 105 insertions(+), 35 deletions(-)

diff --git a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
index 8aa6bb1d..0d637f5f 100644
--- a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
+++ b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php
@@ -123,30 +123,7 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
         $startDate = $this->getStartDateOption($input, $output);
         $endDate = $this->getEndDateOption($input, $output);
         $orderBy = $this->processOrderBy($input);
-
-        $pickProp = static fn (string $prop): callable => static fn (array $shortUrl) => $shortUrl[$prop];
-        $columnMap = [
-            'Short Code' => $pickProp('shortCode'),
-            'Title' => $pickProp('title'),
-            'Short URL' => $pickProp('shortUrl'),
-            'Long URL' => $pickProp('longUrl'),
-            'Date created' => $pickProp('dateCreated'),
-            'Visits count' => $pickProp('visitsCount'),
-        ];
-        if ($this->getOptionWithDeprecatedFallback($input, 'show-tags')) {
-            $columnMap['Tags'] = static fn (array $shortUrl): string => implode(', ', $shortUrl['tags']);
-        }
-        if ($input->getOption('show-api-key')) {
-            $columnMap['API Key'] = static fn (array $_, ShortUrl $shortUrl): string =>
-                (string) $shortUrl->authorApiKey();
-        }
-        if ($input->getOption('show-api-key-name')) {
-            $columnMap['API Key Name'] = static function (array $_, ShortUrl $shortUrl): ?string {
-                $apiKey = $shortUrl->authorApiKey();
-
-                return $apiKey !== null ? $apiKey->name() : null;
-            };
-        }
+        $columnsMap = $this->resolveColumnsMap($input);
 
         $data = [
             ShortUrlsParamsInputFilter::SEARCH_TERM => $searchTerm,
@@ -162,7 +139,7 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
 
         do {
             $data[ShortUrlsParamsInputFilter::PAGE] = $page;
-            $result = $this->renderPage($output, $columnMap, ShortUrlsParams::fromRawData($data), $all);
+            $result = $this->renderPage($output, $columnsMap, ShortUrlsParams::fromRawData($data), $all);
             $page++;
 
             $continue = $result->hasNextPage() && $io->confirm(
@@ -179,19 +156,19 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
 
     private function renderPage(
         OutputInterface $output,
-        array $columnMap,
+        array $columnsMap,
         ShortUrlsParams $params,
         bool $all
     ): Paginator {
         $shortUrls = $this->shortUrlService->listShortUrls($params);
 
-        $rows = map($shortUrls, function (ShortUrl $shortUrl) use ($columnMap) {
+        $rows = map($shortUrls, function (ShortUrl $shortUrl) use ($columnsMap) {
             $rawShortUrl = $this->transformer->transform($shortUrl);
-            return map($columnMap, fn (callable $call) => $call($rawShortUrl, $shortUrl));
+            return map($columnsMap, fn (callable $call) => $call($rawShortUrl, $shortUrl));
         });
 
         ShlinkTable::fromOutput($output)->render(
-            array_keys($columnMap),
+            array_keys($columnsMap),
             $rows,
             $all ? null : $this->formatCurrentPageMessage($shortUrls, 'Page %s of %s'),
         );
@@ -209,4 +186,33 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
         [$field, $dir] = array_pad(explode(',', $orderBy), 2, null);
         return $dir === null ? $field : sprintf('%s-%s', $field, $dir);
     }
+
+    private function resolveColumnsMap(InputInterface $input): array
+    {
+        $pickProp = static fn (string $prop): callable => static fn (array $shortUrl) => $shortUrl[$prop];
+        $columnsMap = [
+            'Short Code' => $pickProp('shortCode'),
+            'Title' => $pickProp('title'),
+            'Short URL' => $pickProp('shortUrl'),
+            'Long URL' => $pickProp('longUrl'),
+            'Date created' => $pickProp('dateCreated'),
+            'Visits count' => $pickProp('visitsCount'),
+        ];
+        if ($this->getOptionWithDeprecatedFallback($input, 'show-tags')) {
+            $columnsMap['Tags'] = static fn (array $shortUrl): string => implode(', ', $shortUrl['tags']);
+        }
+        if ($input->getOption('show-api-key')) {
+            $columnsMap['API Key'] = static fn (array $_, ShortUrl $shortUrl): string =>
+            (string) $shortUrl->authorApiKey();
+        }
+        if ($input->getOption('show-api-key-name')) {
+            $columnsMap['API Key Name'] = static function (array $_, ShortUrl $shortUrl): ?string {
+                $apiKey = $shortUrl->authorApiKey();
+
+                return $apiKey !== null ? $apiKey->name() : null;
+            };
+        }
+
+        return $columnsMap;
+    }
 }
diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php
index 08519b62..6f7b11a6 100644
--- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php
+++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php
@@ -12,13 +12,17 @@ use Prophecy\Prophecy\ObjectProphecy;
 use Shlinkio\Shlink\CLI\Command\ShortUrl\ListShortUrlsCommand;
 use Shlinkio\Shlink\Common\Paginator\Paginator;
 use Shlinkio\Shlink\Core\Entity\ShortUrl;
+use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
 use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
 use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
 use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
 use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
+use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta;
+use Shlinkio\Shlink\Rest\Entity\ApiKey;
 use ShlinkioTest\Shlink\CLI\CliTestUtilsTrait;
 use Symfony\Component\Console\Tester\CommandTester;
 
+use function count;
 use function explode;
 
 class ListShortUrlsCommandTest extends TestCase
@@ -98,17 +102,77 @@ class ListShortUrlsCommandTest extends TestCase
         $this->commandTester->execute(['--page' => $page]);
     }
 
-    /** @test */
-    public function ifTagsFlagIsProvidedTagsColumnIsIncluded(): void
-    {
+    /**
+     * @test
+     * @dataProvider provideOptionalFlags
+     */
+    public function provideOptionalFlagsMakesNewColumnsToBeIncluded(
+        array $input,
+        array $expectedContents,
+        array $notExpectedContents,
+        ApiKey $apiKey
+    ): void {
         $this->shortUrlService->listShortUrls(ShortUrlsParams::emptyInstance())
-            ->willReturn(new Paginator(new ArrayAdapter([])))
+            ->willReturn(new Paginator(new ArrayAdapter([
+                ShortUrl::fromMeta(ShortUrlMeta::fromRawData([
+                    'longUrl' => 'foo.com',
+                    'tags' => ['foo', 'bar', 'baz'],
+                    'apiKey' => $apiKey,
+                ])),
+            ])))
             ->shouldBeCalledOnce();
 
         $this->commandTester->setInputs(['y']);
-        $this->commandTester->execute(['--show-tags' => true]);
+        $this->commandTester->execute($input);
         $output = $this->commandTester->getDisplay();
-        self::assertStringContainsString('Tags', $output);
+
+        if (count($expectedContents) === 0 && count($notExpectedContents) === 0) {
+            self::fail('No expectations were run');
+        }
+
+        foreach ($expectedContents as $column) {
+            self::assertStringContainsString($column, $output);
+        }
+        foreach ($notExpectedContents as $column) {
+            self::assertStringNotContainsString($column, $output);
+        }
+    }
+
+    public function provideOptionalFlags(): iterable
+    {
+        $apiKey = ApiKey::fromMeta(ApiKeyMeta::withName('my api key'));
+        $key = $apiKey->toString();
+
+        yield 'tags only' => [
+            ['--show-tags' => true],
+            ['| Tags    ', '| foo, bar, baz'],
+            ['| API Key    ', '| API Key Name |', $key, '| my api key'],
+            $apiKey,
+        ];
+        yield 'api key only' => [
+            ['--show-api-key' => true],
+            ['| API Key    ', $key],
+            ['| Tags    ', '| foo, bar, baz', '| API Key Name |', '| my api key'],
+            $apiKey,
+        ];
+        yield 'api key name only' => [
+            ['--show-api-key-name' => true],
+            ['| API Key Name |', '| my api key'],
+            ['| Tags    ', '| foo, bar, baz', '| API Key    ', $key],
+            $apiKey,
+        ];
+        yield 'tags and api key' => [
+            ['--show-tags' => true, '--show-api-key' => true],
+            ['| API Key    ', '| Tags    ', '| foo, bar, baz', $key],
+            ['| API Key Name |', '| my api key'],
+            $apiKey,
+        ];
+        yield 'all' => [
+            ['--show-tags' => true, '--show-api-key' => true, '--show-api-key-name' => true],
+            ['| API Key    ', '| Tags    ', '| API Key Name |', '| foo, bar, baz', $key, '| my api key'],
+            [],
+            $apiKey,
+        ];
     }
 
     /**

From d751df70fdca8230c71ccac35e3be0a8f2e09c33 Mon Sep 17 00:00:00 2001
From: Alejandro Celaya <alejandro@alejandrocelaya.com>
Date: Sun, 11 Apr 2021 11:30:43 +0200
Subject: [PATCH 5/5] Updated changelog

---
 CHANGELOG.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c88f036..5c0b0075 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
 
     Also, when using swoole, the file is now updated **after** tracking a visit, which means it will not apply until the next one.
 
+* [#1059](https://github.com/shlinkio/shlink/issues/1059) Added ability to optionally display author API key and its name when listing short URLs from the command line.
+
 ### Changed
 * [#1036](https://github.com/shlinkio/shlink/issues/1036) Updated to `happyr/doctrine-specification` 2.0.
 * [#1039](https://github.com/shlinkio/shlink/issues/1039) Updated to `endroid/qr-code` 4.0.