mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 12:11:19 +03:00
Allow type filter property for orphan visits list
This commit is contained in:
parent
46acf4de1c
commit
48a8290e92
12 changed files with 144 additions and 16 deletions
|
@ -199,7 +199,7 @@ services:
|
|||
|
||||
shlink_swagger_ui:
|
||||
container_name: shlink_swagger_ui
|
||||
image: swaggerapi/swagger-ui:v5.10.3
|
||||
image: swaggerapi/swagger-ui:v5.11.3
|
||||
ports:
|
||||
- "8005:8080"
|
||||
volumes:
|
||||
|
|
|
@ -55,6 +55,16 @@
|
|||
"type": "string",
|
||||
"enum": ["true"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"in": "query",
|
||||
"description": "The type of visits to return. All visits are returned when not provided.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["invalid_short_url", "base_url", "regular_404"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"security": [
|
||||
|
@ -137,6 +147,54 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Provided query arguments are invalid.",
|
||||
"content": {
|
||||
"application/problem+json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "../definitions/Error.json"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["invalidElements"],
|
||||
"properties": {
|
||||
"invalidElements": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["type"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"examples": {
|
||||
"API v3 and newer": {
|
||||
"value": {
|
||||
"title": "Invalid data",
|
||||
"type": "https://shlink.io/api/error/invalid-data",
|
||||
"detail": "Provided data is not valid",
|
||||
"status": 400,
|
||||
"invalidElements": ["type"]
|
||||
}
|
||||
},
|
||||
"Previous to API v3": {
|
||||
"value": {
|
||||
"title": "Invalid data",
|
||||
"type": "INVALID_ARGUMENT",
|
||||
"detail": "Provided data is not valid",
|
||||
"status": 400,
|
||||
"invalidElements": ["type"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Unexpected error.",
|
||||
"content": {
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Shlinkio\Shlink\CLI\Command\Visit;
|
|||
use Shlinkio\Shlink\Common\Paginator\Paginator;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
||||
class GetOrphanVisitsCommand extends AbstractVisitsListCommand
|
||||
|
@ -23,7 +23,7 @@ class GetOrphanVisitsCommand extends AbstractVisitsListCommand
|
|||
|
||||
protected function getVisitsPaginator(InputInterface $input, DateRange $dateRange): Paginator
|
||||
{
|
||||
return $this->visitsHelper->orphanVisits(new VisitsParams($dateRange));
|
||||
return $this->visitsHelper->orphanVisits(new OrphanVisitsParams($dateRange));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
53
module/Core/src/Visit/Model/OrphanVisitsParams.php
Normal file
53
module/Core/src/Visit/Model/OrphanVisitsParams.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Visit\Model;
|
||||
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Exception\ValidationException;
|
||||
use ValueError;
|
||||
|
||||
use function implode;
|
||||
use function Shlinkio\Shlink\Core\enumValues;
|
||||
use function sprintf;
|
||||
|
||||
final class OrphanVisitsParams extends VisitsParams
|
||||
{
|
||||
public function __construct(
|
||||
?DateRange $dateRange = null,
|
||||
?int $page = null,
|
||||
?int $itemsPerPage = null,
|
||||
bool $excludeBots = false,
|
||||
public readonly ?OrphanVisitType $type = null,
|
||||
) {
|
||||
parent::__construct($dateRange, $page, $itemsPerPage, $excludeBots);
|
||||
}
|
||||
|
||||
public static function fromRawData(array $query): self
|
||||
{
|
||||
$visitsParams = parent::fromRawData($query);
|
||||
$type = $query['type'] ?? null;
|
||||
|
||||
return new self(
|
||||
dateRange: $visitsParams->dateRange,
|
||||
page: $visitsParams->page,
|
||||
itemsPerPage: $visitsParams->itemsPerPage,
|
||||
excludeBots: $visitsParams->excludeBots,
|
||||
type: $type !== null ? self::parseType($type) : null,
|
||||
);
|
||||
}
|
||||
|
||||
private static function parseType(string $type): OrphanVisitType
|
||||
{
|
||||
try {
|
||||
return OrphanVisitType::from($type);
|
||||
} catch (ValueError) {
|
||||
throw ValidationException::fromArray([
|
||||
'type' => sprintf(
|
||||
'%s is not a valid orphan visit type. Expected one of ["%s"]',
|
||||
$type,
|
||||
implode('", "', enumValues(OrphanVisitType::class)),
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ use Shlinkio\Shlink\Core\Model\AbstractInfinitePaginableListParams;
|
|||
|
||||
use function Shlinkio\Shlink\Core\parseDateRangeFromQuery;
|
||||
|
||||
final class VisitsParams extends AbstractInfinitePaginableListParams
|
||||
class VisitsParams extends AbstractInfinitePaginableListParams
|
||||
{
|
||||
public readonly DateRange $dateRange;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||
namespace Shlinkio\Shlink\Core\Visit\Paginator\Adapter;
|
||||
|
||||
use Shlinkio\Shlink\Core\Paginator\Adapter\AbstractCacheableCountPaginatorAdapter;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\OrphanVisitsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\OrphanVisitsListFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface;
|
||||
|
@ -15,7 +15,7 @@ class OrphanVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
|
|||
{
|
||||
public function __construct(
|
||||
private readonly VisitRepositoryInterface $repo,
|
||||
private readonly VisitsParams $params,
|
||||
private readonly OrphanVisitsParams $params,
|
||||
private readonly ?ApiKey $apiKey,
|
||||
) {
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ class OrphanVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
|
|||
dateRange: $this->params->dateRange,
|
||||
excludeBots: $this->params->excludeBots,
|
||||
apiKey: $this->apiKey,
|
||||
type: $this->params->type,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -35,6 +36,7 @@ class OrphanVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
|
|||
dateRange: $this->params->dateRange,
|
||||
excludeBots: $this->params->excludeBots,
|
||||
apiKey: $this->apiKey,
|
||||
type: $this->params->type,
|
||||
limit: $length,
|
||||
offset: $offset,
|
||||
));
|
||||
|
|
|
@ -18,6 +18,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface;
|
|||
use Shlinkio\Shlink\Core\Tag\Entity\Tag;
|
||||
use Shlinkio\Shlink\Core\Tag\Repository\TagRepository;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsStats;
|
||||
use Shlinkio\Shlink\Core\Visit\Paginator\Adapter\DomainVisitsPaginatorAdapter;
|
||||
|
@ -117,7 +118,7 @@ class VisitsStatsHelper implements VisitsStatsHelperInterface
|
|||
/**
|
||||
* @return Visit[]|Paginator
|
||||
*/
|
||||
public function orphanVisits(VisitsParams $params, ?ApiKey $apiKey = null): Paginator
|
||||
public function orphanVisits(OrphanVisitsParams $params, ?ApiKey $apiKey = null): Paginator
|
||||
{
|
||||
/** @var VisitRepositoryInterface $repo */
|
||||
$repo = $this->em->getRepository(Visit::class);
|
||||
|
|
|
@ -10,6 +10,7 @@ use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
|||
use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsStats;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
@ -43,7 +44,7 @@ interface VisitsStatsHelperInterface
|
|||
/**
|
||||
* @return Visit[]|Paginator
|
||||
*/
|
||||
public function orphanVisits(VisitsParams $params, ?ApiKey $apiKey = null): Paginator;
|
||||
public function orphanVisits(OrphanVisitsParams $params, ?ApiKey $apiKey = null): Paginator;
|
||||
|
||||
/**
|
||||
* @return Visit[]|Paginator
|
||||
|
|
|
@ -9,8 +9,8 @@ use PHPUnit\Framework\Attributes\Test;
|
|||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Paginator\Adapter\OrphanVisitsPaginatorAdapter;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\OrphanVisitsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\OrphanVisitsListFiltering;
|
||||
|
@ -21,13 +21,13 @@ class OrphanVisitsPaginatorAdapterTest extends TestCase
|
|||
{
|
||||
private OrphanVisitsPaginatorAdapter $adapter;
|
||||
private MockObject & VisitRepositoryInterface $repo;
|
||||
private VisitsParams $params;
|
||||
private OrphanVisitsParams $params;
|
||||
private ApiKey $apiKey;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repo = $this->createMock(VisitRepositoryInterface::class);
|
||||
$this->params = VisitsParams::fromRawData([]);
|
||||
$this->params = OrphanVisitsParams::fromRawData([]);
|
||||
$this->apiKey = ApiKey::create();
|
||||
|
||||
$this->adapter = new OrphanVisitsPaginatorAdapter($this->repo, $this->params, $this->apiKey);
|
||||
|
|
|
@ -23,6 +23,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface;
|
|||
use Shlinkio\Shlink\Core\Tag\Entity\Tag;
|
||||
use Shlinkio\Shlink\Core\Tag\Repository\TagRepository;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsStats;
|
||||
|
@ -260,7 +261,7 @@ class VisitsStatsHelperTest extends TestCase
|
|||
)->willReturn($list);
|
||||
$this->em->expects($this->once())->method('getRepository')->with(Visit::class)->willReturn($repo);
|
||||
|
||||
$paginator = $this->helper->orphanVisits(new VisitsParams());
|
||||
$paginator = $this->helper->orphanVisits(new OrphanVisitsParams());
|
||||
|
||||
self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults()));
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use Psr\Http\Message\ResponseInterface;
|
|||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtilsTrait;
|
||||
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
|
||||
|
@ -29,7 +29,7 @@ class OrphanVisitsAction extends AbstractRestAction
|
|||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$params = VisitsParams::fromRawData($request->getQueryParams());
|
||||
$params = OrphanVisitsParams::fromRawData($request->getQueryParams());
|
||||
$apiKey = AuthenticationMiddleware::apiKeyFromRequest($request);
|
||||
$visits = $this->visitsHelper->orphanVisits($params, $apiKey);
|
||||
|
||||
|
|
|
@ -12,9 +12,10 @@ use PHPUnit\Framework\MockObject\MockObject;
|
|||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Paginator\Paginator;
|
||||
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
|
||||
use Shlinkio\Shlink\Core\Exception\ValidationException;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Visit\Model\VisitsParams;
|
||||
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\Visit\OrphanVisitsAction;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
@ -41,7 +42,7 @@ class OrphanVisitsActionTest extends TestCase
|
|||
$visitor = Visitor::emptyInstance();
|
||||
$visits = [Visit::forInvalidShortUrl($visitor), Visit::forRegularNotFound($visitor)];
|
||||
$this->visitsHelper->expects($this->once())->method('orphanVisits')->with(
|
||||
$this->isInstanceOf(VisitsParams::class),
|
||||
$this->isInstanceOf(OrphanVisitsParams::class),
|
||||
)->willReturn(new Paginator(new ArrayAdapter($visits)));
|
||||
$visitsAmount = count($visits);
|
||||
$this->orphanVisitTransformer->expects($this->exactly($visitsAmount))->method('transform')->with(
|
||||
|
@ -57,4 +58,15 @@ class OrphanVisitsActionTest extends TestCase
|
|||
self::assertCount($visitsAmount, $payload['visits']['data']);
|
||||
self::assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function exceptionIsThrownIfInvalidDataIsProvided(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->action->handle(
|
||||
ServerRequestFactory::fromGlobals()
|
||||
->withAttribute(ApiKey::class, ApiKey::create())
|
||||
->withQueryParams(['type' => 'invalidType']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue