Skip to content

Commit 7ca65cf

Browse files
authored
Merge pull request #76 from itk-dev/feature/server-type-filter
feat: add server.type filter and sort to Installation, Site, Domain
2 parents 038dea0 + 33836c3 commit 7ca65cf

6 files changed

Lines changed: 120 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
- [#77](https://github.com/itk-dev/devops_itksites/pull/77)
1111
Fix SemverFilter: respect value2 with directional operators
12-
12+
- [#76](https://github.com/itk-dev/devops_itksites/pull/76)
13+
Add server type filter and sort on Installation, Site, Domain
1314
- [#75](https://github.com/itk-dev/devops_itksites/pull/75)
1415
Add semver-aware filter on every admin version column, make version
1516
column semver sortable

src/Controller/Admin/DomainCrudController.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use App\Admin\Field\ServerTypeField;
99
use App\Admin\Field\SiteTypeField;
1010
use App\Entity\Domain;
11+
use App\Form\Type\Admin\ServerTypeFilter;
1112
use App\Trait\ExportCrudControllerTrait;
1213
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
1314
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
@@ -52,7 +53,7 @@ public function configureFields(string $pageName): iterable
5253
yield DomainField::new('address')->setColumns(12);
5354
yield SiteTypeField::new('site.type')->hideOnIndex();
5455
yield AssociationField::new('site')->hideOnIndex();
55-
yield ServerTypeField::new('server.type')->setLabel('Type');
56+
yield ServerTypeField::new('server.type')->setLabel('Type')->setSortable(true);
5657
yield AssociationField::new('server');
5758
yield AssociationField::new('detectionResult')->hideOnIndex();
5859
yield DateTimeField::new('createdAt')->hideOnIndex();
@@ -66,6 +67,7 @@ public function configureFilters(Filters $filters): Filters
6667
->add('address')
6768
->add('site')
6869
->add('server')
70+
->add(ServerTypeFilter::new('server.type', 'Server type'))
6971
;
7072
}
7173
}

src/Controller/Admin/InstallationCrudController.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use App\Entity\Installation;
1313
use App\Form\Type\Admin\FrameworkFilter;
1414
use App\Form\Type\Admin\SemverFilter;
15+
use App\Form\Type\Admin\ServerTypeFilter;
1516
use App\Trait\ExportCrudControllerTrait;
1617
use App\Trait\SemverSortableCrudControllerTrait;
1718
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
@@ -68,7 +69,7 @@ public function configureFields(string $pageName): iterable
6869
yield CodeEditorField::new('gitChanges')->hideOnIndex();
6970
yield AssociationField::new('sites')->hideOnIndex();
7071
yield RootDirField::new('rootDir')->setColumns(12);
71-
yield ServerTypeField::new('server.type')->setLabel('Type');
72+
yield ServerTypeField::new('server.type')->setLabel('Type')->setSortable(true);
7273
yield AssociationField::new('server');
7374
yield AssociationField::new('detectionResult')->hideOnIndex();
7475
yield DateTimeField::new('createdAt')->hideOnIndex();
@@ -86,6 +87,7 @@ public function configureFilters(Filters $filters): Filters
8687
->add(SemverFilter::new('composerVersion', 'Comp.'))
8788
->add('rootDir')
8889
->add('server')
90+
->add(ServerTypeFilter::new('server.type', 'Server type'))
8991
// ->add(SystemFilter::new('system')->mapped(false))
9092
;
9193
}

src/Controller/Admin/SiteCrudController.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use App\Admin\Field\VersionField;
1414
use App\Entity\Site;
1515
use App\Form\Type\Admin\SemverFilter;
16+
use App\Form\Type\Admin\ServerTypeFilter;
1617
use App\Trait\ExportCrudControllerTrait;
1718
use App\Trait\SemverSortableCrudControllerTrait;
1819
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
@@ -67,7 +68,7 @@ public function configureFields(string $pageName): iterable
6768
yield RootDirField::new('rootDir')->setColumns(12)->hideOnIndex();
6869
yield VersionField::new('phpVersion')->setLabel('PHP');
6970
yield AssociationField::new('installation')->hideOnIndex();
70-
yield ServerTypeField::new('server.type')->setLabel('Type');
71+
yield ServerTypeField::new('server.type')->setLabel('Type')->setSortable(true);
7172
yield AssociationField::new('server');
7273
yield AssociationField::new('detectionResult')->hideOnIndex();
7374
yield DateTimeField::new('createdAt')->hideOnIndex();
@@ -80,7 +81,8 @@ public function configureFilters(Filters $filters): Filters
8081
->add('primaryDomain')
8182
->add('configFilePath')
8283
->add(SemverFilter::new('phpVersion', 'PHP'))
83-
->add('server');
84+
->add('server')
85+
->add(ServerTypeFilter::new('server.type', 'Server type'));
8486
}
8587

8688
#[\Override]

src/Form/Type/Admin/ServerTypeFilter.php

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,32 @@ public static function new(string $propertyName, false|string|TranslatableInterf
2727

2828
public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto, ?FieldDto $fieldDto, EntityDto $entityDto): void
2929
{
30-
$queryBuilder->andWhere(sprintf('%s.%s = :type', $filterDataDto->getEntityAlias(), $filterDataDto->getProperty()))
31-
->setParameter('type', $filterDataDto->getValue());
30+
$rootAlias = $filterDataDto->getEntityAlias();
31+
$property = $filterDataDto->getProperty();
32+
$parameter = $filterDataDto->getParameterName();
33+
$value = $filterDataDto->getValue();
34+
35+
// EasyAdmin doesn't auto-join nested-property filters; if the
36+
// property crosses a relation (e.g. "server.type"), join it
37+
// ourselves and reference the joined alias. We reuse the plain
38+
// relation name as the join alias to match EA's own sort-side
39+
// auto-join (see EntityRepository::addOrderClause), so combining
40+
// sort + filter on the same association produces a single JOIN.
41+
// Background: https://github.com/EasyCorp/EasyAdminBundle/issues/4120.
42+
if (str_contains($property, '.')) {
43+
[$relation, $leafProperty] = explode('.', $property, 2);
44+
if (!in_array($relation, $queryBuilder->getAllAliases(), true)) {
45+
$queryBuilder->leftJoin($rootAlias.'.'.$relation, $relation);
46+
}
47+
$queryBuilder
48+
->andWhere(sprintf('%s.%s = :%s', $relation, $leafProperty, $parameter))
49+
->setParameter($parameter, $value);
50+
51+
return;
52+
}
53+
54+
$queryBuilder
55+
->andWhere(sprintf('%s.%s = :%s', $rootAlias, $property, $parameter))
56+
->setParameter($parameter, $value);
3257
}
3358
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Tests\Form\Type\Admin;
6+
7+
use App\Entity\Installation;
8+
use App\Form\Type\Admin\ServerTypeFilter;
9+
use App\Form\Type\Admin\ServerTypeFilterType;
10+
use Doctrine\ORM\EntityManagerInterface;
11+
use Doctrine\ORM\QueryBuilder;
12+
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
13+
use EasyCorp\Bundle\EasyAdminBundle\Dto\FilterDataDto;
14+
use EasyCorp\Bundle\EasyAdminBundle\Dto\FilterDto;
15+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
16+
17+
class ServerTypeFilterTest extends KernelTestCase
18+
{
19+
public function testApplyOnFlatPropertyComparesAgainstRootAlias(): void
20+
{
21+
$qb = $this->makeInstallationQueryBuilder();
22+
23+
$this->apply($qb, 'type', 'prod');
24+
25+
$dql = $qb->getDQL();
26+
self::assertStringContainsString('entity.type = :type_0', $dql);
27+
self::assertStringNotContainsString('JOIN', $dql, 'A flat property must not trigger any JOIN');
28+
self::assertSame('prod', $qb->getParameter('type_0')?->getValue());
29+
}
30+
31+
public function testApplyOnNestedPropertyJoinsTheRelation(): void
32+
{
33+
$qb = $this->makeInstallationQueryBuilder();
34+
35+
$this->apply($qb, 'server.type', 'stg');
36+
37+
$dql = $qb->getDQL();
38+
self::assertStringContainsString('LEFT JOIN entity.server server', $dql, 'Nested filter must auto-join the relation');
39+
self::assertStringContainsString('server.type = :server_type_0', $dql);
40+
self::assertSame('stg', $qb->getParameter('server_type_0')?->getValue());
41+
}
42+
43+
public function testApplyReusesExistingJoinAliasInsteadOfDuplicating(): void
44+
{
45+
$qb = $this->makeInstallationQueryBuilder()
46+
->leftJoin('entity.server', 'server');
47+
48+
$this->apply($qb, 'server.type', 'devops');
49+
50+
$dql = $qb->getDQL();
51+
self::assertSame(1, substr_count($dql, 'LEFT JOIN entity.server'), 'No duplicate join when alias already exists');
52+
self::assertStringContainsString('server.type = :server_type_0', $dql);
53+
}
54+
55+
private function apply(QueryBuilder $qb, string $property, string $value): void
56+
{
57+
$filterDto = new FilterDto();
58+
$filterDto->setProperty($property);
59+
$filterDto->setFormType(ServerTypeFilterType::class);
60+
61+
ServerTypeFilter::new($property)->apply(
62+
$qb,
63+
FilterDataDto::new(0, $filterDto, 'entity', [
64+
'comparison' => '=',
65+
'value' => $value,
66+
]),
67+
null,
68+
new EntityDto(Installation::class, $this->getEntityManager()->getClassMetadata(Installation::class)),
69+
);
70+
}
71+
72+
private function makeInstallationQueryBuilder(): QueryBuilder
73+
{
74+
return $this->getEntityManager()->getRepository(Installation::class)->createQueryBuilder('entity');
75+
}
76+
77+
private function getEntityManager(): EntityManagerInterface
78+
{
79+
return static::getContainer()->get('doctrine')->getManager();
80+
}
81+
}

0 commit comments

Comments
 (0)