Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- [#77](https://github.com/itk-dev/devops_itksites/pull/77)
Fix SemverFilter: respect value2 with directional operators

- [#76](https://github.com/itk-dev/devops_itksites/pull/76)
Add server type filter and sort on Installation, Site, Domain
- [#75](https://github.com/itk-dev/devops_itksites/pull/75)
Add semver-aware filter on every admin version column, make version
column semver sortable
Expand Down
4 changes: 3 additions & 1 deletion src/Controller/Admin/DomainCrudController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Admin\Field\ServerTypeField;
use App\Admin\Field\SiteTypeField;
use App\Entity\Domain;
use App\Form\Type\Admin\ServerTypeFilter;
use App\Trait\ExportCrudControllerTrait;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
Expand Down Expand Up @@ -52,7 +53,7 @@ public function configureFields(string $pageName): iterable
yield DomainField::new('address')->setColumns(12);
yield SiteTypeField::new('site.type')->hideOnIndex();
yield AssociationField::new('site')->hideOnIndex();
yield ServerTypeField::new('server.type')->setLabel('Type');
yield ServerTypeField::new('server.type')->setLabel('Type')->setSortable(true);
yield AssociationField::new('server');
yield AssociationField::new('detectionResult')->hideOnIndex();
yield DateTimeField::new('createdAt')->hideOnIndex();
Expand All @@ -66,6 +67,7 @@ public function configureFilters(Filters $filters): Filters
->add('address')
->add('site')
->add('server')
->add(ServerTypeFilter::new('server.type', 'Server type'))
;
}
}
4 changes: 3 additions & 1 deletion src/Controller/Admin/InstallationCrudController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use App\Entity\Installation;
use App\Form\Type\Admin\FrameworkFilter;
use App\Form\Type\Admin\SemverFilter;
use App\Form\Type\Admin\ServerTypeFilter;
use App\Trait\ExportCrudControllerTrait;
use App\Trait\SemverSortableCrudControllerTrait;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
Expand Down Expand Up @@ -68,7 +69,7 @@ public function configureFields(string $pageName): iterable
yield CodeEditorField::new('gitChanges')->hideOnIndex();
yield AssociationField::new('sites')->hideOnIndex();
yield RootDirField::new('rootDir')->setColumns(12);
yield ServerTypeField::new('server.type')->setLabel('Type');
yield ServerTypeField::new('server.type')->setLabel('Type')->setSortable(true);
yield AssociationField::new('server');
yield AssociationField::new('detectionResult')->hideOnIndex();
yield DateTimeField::new('createdAt')->hideOnIndex();
Expand All @@ -86,6 +87,7 @@ public function configureFilters(Filters $filters): Filters
->add(SemverFilter::new('composerVersion', 'Comp.'))
->add('rootDir')
->add('server')
->add(ServerTypeFilter::new('server.type', 'Server type'))
// ->add(SystemFilter::new('system')->mapped(false))
;
}
Expand Down
6 changes: 4 additions & 2 deletions src/Controller/Admin/SiteCrudController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use App\Admin\Field\VersionField;
use App\Entity\Site;
use App\Form\Type\Admin\SemverFilter;
use App\Form\Type\Admin\ServerTypeFilter;
use App\Trait\ExportCrudControllerTrait;
use App\Trait\SemverSortableCrudControllerTrait;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
Expand Down Expand Up @@ -67,7 +68,7 @@ public function configureFields(string $pageName): iterable
yield RootDirField::new('rootDir')->setColumns(12)->hideOnIndex();
yield VersionField::new('phpVersion')->setLabel('PHP');
yield AssociationField::new('installation')->hideOnIndex();
yield ServerTypeField::new('server.type')->setLabel('Type');
yield ServerTypeField::new('server.type')->setLabel('Type')->setSortable(true);
yield AssociationField::new('server');
yield AssociationField::new('detectionResult')->hideOnIndex();
yield DateTimeField::new('createdAt')->hideOnIndex();
Expand All @@ -80,7 +81,8 @@ public function configureFilters(Filters $filters): Filters
->add('primaryDomain')
->add('configFilePath')
->add(SemverFilter::new('phpVersion', 'PHP'))
->add('server');
->add('server')
->add(ServerTypeFilter::new('server.type', 'Server type'));
}

#[\Override]
Expand Down
29 changes: 27 additions & 2 deletions src/Form/Type/Admin/ServerTypeFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,32 @@ public static function new(string $propertyName, false|string|TranslatableInterf

public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto, ?FieldDto $fieldDto, EntityDto $entityDto): void
{
$queryBuilder->andWhere(sprintf('%s.%s = :type', $filterDataDto->getEntityAlias(), $filterDataDto->getProperty()))
->setParameter('type', $filterDataDto->getValue());
$rootAlias = $filterDataDto->getEntityAlias();
$property = $filterDataDto->getProperty();
$parameter = $filterDataDto->getParameterName();
$value = $filterDataDto->getValue();

// EasyAdmin doesn't auto-join nested-property filters; if the
// property crosses a relation (e.g. "server.type"), join it
// ourselves and reference the joined alias. We reuse the plain
// relation name as the join alias to match EA's own sort-side
// auto-join (see EntityRepository::addOrderClause), so combining
// sort + filter on the same association produces a single JOIN.
// Background: https://github.com/EasyCorp/EasyAdminBundle/issues/4120.
if (str_contains($property, '.')) {
[$relation, $leafProperty] = explode('.', $property, 2);
if (!in_array($relation, $queryBuilder->getAllAliases(), true)) {
$queryBuilder->leftJoin($rootAlias.'.'.$relation, $relation);
}
$queryBuilder
->andWhere(sprintf('%s.%s = :%s', $relation, $leafProperty, $parameter))
->setParameter($parameter, $value);

return;
}

$queryBuilder
->andWhere(sprintf('%s.%s = :%s', $rootAlias, $property, $parameter))
->setParameter($parameter, $value);
}
}
81 changes: 81 additions & 0 deletions tests/Form/Type/Admin/ServerTypeFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace App\Tests\Form\Type\Admin;

use App\Entity\Installation;
use App\Form\Type\Admin\ServerTypeFilter;
use App\Form\Type\Admin\ServerTypeFilterType;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FilterDataDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FilterDto;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class ServerTypeFilterTest extends KernelTestCase
{
public function testApplyOnFlatPropertyComparesAgainstRootAlias(): void
{
$qb = $this->makeInstallationQueryBuilder();

$this->apply($qb, 'type', 'prod');

$dql = $qb->getDQL();
self::assertStringContainsString('entity.type = :type_0', $dql);
self::assertStringNotContainsString('JOIN', $dql, 'A flat property must not trigger any JOIN');
self::assertSame('prod', $qb->getParameter('type_0')?->getValue());
}

public function testApplyOnNestedPropertyJoinsTheRelation(): void
{
$qb = $this->makeInstallationQueryBuilder();

$this->apply($qb, 'server.type', 'stg');

$dql = $qb->getDQL();
self::assertStringContainsString('LEFT JOIN entity.server server', $dql, 'Nested filter must auto-join the relation');
self::assertStringContainsString('server.type = :server_type_0', $dql);
self::assertSame('stg', $qb->getParameter('server_type_0')?->getValue());
}

public function testApplyReusesExistingJoinAliasInsteadOfDuplicating(): void
{
$qb = $this->makeInstallationQueryBuilder()
->leftJoin('entity.server', 'server');

$this->apply($qb, 'server.type', 'devops');

$dql = $qb->getDQL();
self::assertSame(1, substr_count($dql, 'LEFT JOIN entity.server'), 'No duplicate join when alias already exists');
self::assertStringContainsString('server.type = :server_type_0', $dql);
}

private function apply(QueryBuilder $qb, string $property, string $value): void
{
$filterDto = new FilterDto();
$filterDto->setProperty($property);
$filterDto->setFormType(ServerTypeFilterType::class);

ServerTypeFilter::new($property)->apply(
$qb,
FilterDataDto::new(0, $filterDto, 'entity', [
'comparison' => '=',
'value' => $value,
]),
null,
new EntityDto(Installation::class, $this->getEntityManager()->getClassMetadata(Installation::class)),
);
}

private function makeInstallationQueryBuilder(): QueryBuilder
{
return $this->getEntityManager()->getRepository(Installation::class)->createQueryBuilder('entity');
}

private function getEntityManager(): EntityManagerInterface
{
return static::getContainer()->get('doctrine')->getManager();
}
}
Loading