Skip to content

Commit 7bcceab

Browse files
committed
Create package link pages
1 parent 292e03d commit 7bcceab

File tree

7 files changed

+229
-6
lines changed

7 files changed

+229
-6
lines changed

src/Controller/Dashboard/DashboardPackagesInfoController.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,27 @@
33
namespace CodedMonkey\Dirigent\Controller\Dashboard;
44

55
use CodedMonkey\Dirigent\Attribute\IsGrantedAccess;
6+
use CodedMonkey\Dirigent\Doctrine\Entity\Dependent;
67
use CodedMonkey\Dirigent\Doctrine\Entity\Package;
8+
use CodedMonkey\Dirigent\Doctrine\Entity\PackageProvideLink;
9+
use CodedMonkey\Dirigent\Doctrine\Entity\PackageRequireLink;
10+
use CodedMonkey\Dirigent\Doctrine\Entity\PackageSuggestLink;
11+
use CodedMonkey\Dirigent\Doctrine\Entity\Provider;
12+
use CodedMonkey\Dirigent\Doctrine\Entity\Suggester;
713
use CodedMonkey\Dirigent\Doctrine\Repository\PackageRepository;
14+
use CodedMonkey\Dirigent\EasyAdmin\PackagePaginator;
815
use Composer\Semver\VersionParser;
16+
use Doctrine\ORM\EntityManagerInterface;
17+
use Doctrine\ORM\QueryBuilder;
918
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
19+
use Symfony\Component\HttpFoundation\Request;
1020
use Symfony\Component\HttpFoundation\Response;
1121
use Symfony\Component\Routing\Attribute\Route;
1222

1323
class DashboardPackagesInfoController extends AbstractController
1424
{
1525
public function __construct(
26+
private readonly EntityManagerInterface $entityManager,
1627
private readonly PackageRepository $packageRepository,
1728
) {
1829
}
@@ -37,9 +48,19 @@ public function versionInfo(string $packageName, string $packageVersion): Respon
3748
$package = $this->packageRepository->findOneBy(['name' => $packageName]);
3849
$version = $package->getVersion((new VersionParser())->normalize($packageVersion));
3950

51+
$dependentCount = $this->entityManager->getRepository(PackageRequireLink::class)->count(['dependentPackageName' => $package->getName()]);
52+
$implementationCount = $this->entityManager->getRepository(PackageProvideLink::class)->count(['providedPackageName' => $package->getName(), 'implementation' => true]);
53+
$providerCount = $this->entityManager->getRepository(PackageProvideLink::class)->count(['providedPackageName' => $package->getName(), 'implementation' => false]);
54+
$suggesterCount = $this->entityManager->getRepository(PackageSuggestLink::class)->count(['suggestedPackageName' => $package->getName()]);
55+
4056
return $this->render('dashboard/packages/package_info.html.twig', [
4157
'package' => $package,
4258
'version' => $version,
59+
60+
'dependentCount' => $dependentCount,
61+
'implementationCount' => $implementationCount,
62+
'providerCount' => $providerCount,
63+
'suggesterCount' => $suggesterCount,
4364
]);
4465
}
4566

@@ -58,6 +79,60 @@ public function versions(string $packageName): Response
5879
]);
5980
}
6081

82+
#[Route('/packages/{packageName}/dependents', name: 'dashboard_packages_dependents', requirements: ['packageName' => '[a-z0-9_.-]+/[a-z0-9_.-]+'])]
83+
#[IsGrantedAccess]
84+
public function dependents(Request $request, string $packageName): Response
85+
{
86+
$package = $this->packageRepository->findOneBy(['name' => $packageName]);
87+
88+
return $this->packageLinks($request, $package, PackageProvideLink::class, 'Dependents');
89+
}
90+
91+
#[Route('/packages/{packageName}/implementations', name: 'dashboard_packages_implementations', requirements: ['packageName' => '[a-z0-9_.-]+/[a-z0-9_.-]+'])]
92+
#[IsGrantedAccess]
93+
public function implementations(Request $request, string $packageName): Response
94+
{
95+
$package = $this->packageRepository->findOneBy(['name' => $packageName]);
96+
97+
$providerRepository = $this->entityManager->getRepository(Provider::class);
98+
$queryBuilder = $providerRepository->createQueryBuilder('provider');
99+
$queryBuilder
100+
->leftJoin('provider.package', 'package')
101+
->andWhere('provider.providedPackageName = :packageName')
102+
->andWhere('provider.implementation = true')
103+
->setParameter('packageName', $package->getName())
104+
->addOrderBy('package.name', 'ASC');
105+
106+
return $this->packageLinks($request, $package, PackageProvideLink::class, 'Implementations', queryBuilder: $queryBuilder);
107+
}
108+
109+
#[Route('/packages/{packageName}/providers', name: 'dashboard_packages_providers', requirements: ['packageName' => '[a-z0-9_.-]+/[a-z0-9_.-]+'])]
110+
#[IsGrantedAccess]
111+
public function providers(Request $request, string $packageName): Response
112+
{
113+
$package = $this->packageRepository->findOneBy(['name' => $packageName]);
114+
115+
$providerRepository = $this->entityManager->getRepository(Provider::class);
116+
$queryBuilder = $providerRepository->createQueryBuilder('provider');
117+
$queryBuilder
118+
->leftJoin('provider.package', 'package')
119+
->andWhere('provider.providedPackageName = :packageName')
120+
->andWhere('provider.implementation = false')
121+
->setParameter('packageName', $package->getName())
122+
->addOrderBy('package.name', 'ASC');
123+
124+
return $this->packageLinks($request, $package, PackageProvideLink::class, 'Providers', queryBuilder: $queryBuilder);
125+
}
126+
127+
#[Route('/packages/{packageName}/suggesters', name: 'dashboard_packages_suggesters', requirements: ['packageName' => '[a-z0-9_.-]+/[a-z0-9_.-]+'])]
128+
#[IsGrantedAccess]
129+
public function suggesters(Request $request, string $packageName): Response
130+
{
131+
$package = $this->packageRepository->findOneBy(['name' => $packageName]);
132+
133+
return $this->packageLinks($request, $package, PackageSuggestLink::class, 'Suggesters');
134+
}
135+
61136
#[Route('/packages/{packageName}/statistics', name: 'dashboard_packages_statistics', requirements: ['packageName' => '[a-z0-9_.-]+/[a-z0-9_.-]+'])]
62137
#[IsGrantedAccess]
63138
public function statistics(string $packageName): Response
@@ -99,4 +174,27 @@ public function statistics(string $packageName): Response
99174
'installationsToday' => $installationsToday,
100175
]);
101176
}
177+
178+
private function packageLinks(Request $request, Package $package, string $linkClass, string $title, ?QueryBuilder $queryBuilder = null): Response
179+
{
180+
if (!$queryBuilder) {
181+
$dependentRepository = $this->entityManager->getRepository($linkClass);
182+
$queryBuilder = $dependentRepository->createQueryBuilder('link');
183+
$queryBuilder
184+
->leftJoin('link.package', 'package')
185+
->andWhere('link.linkedPackageName = :packageName')
186+
->setParameter('packageName', $package->getName())
187+
->addOrderBy('package.name', 'ASC');
188+
}
189+
190+
$paginator = PackagePaginator::fromRequest($request, $queryBuilder, $this->container->get('router'));
191+
$packageLinks = $paginator->getResults();
192+
193+
return $this->render('dashboard/packages/package_links.html.twig', [
194+
'package' => $package,
195+
'packageLinks' => $packageLinks,
196+
'paginator' => $paginator,
197+
'title' => $title,
198+
]);
199+
}
102200
}

src/Doctrine/Entity/VersionProvideLink.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,14 @@ class VersionProvideLink extends AbstractVersionLink
1010
#[ORM\ManyToOne(targetEntity: Version::class, inversedBy: 'provide')]
1111
#[ORM\JoinColumn(nullable: false)]
1212
protected Version $version;
13+
14+
public function isImplementation(): bool
15+
{
16+
return str_ends_with($this->getLinkedPackageName(), '-implementation');
17+
}
18+
19+
public function getImplementedPackageName(): string
20+
{
21+
return substr($this->getLinkedPackageName(), 0, -15);
22+
}
1323
}

templates/dashboard/packages/_package_header.html.twig

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
<a {% if currentPage == 'versions' %}class="nav-link active" aria-current="page"{% else %}class="nav-link text-primary"{% endif%} href="{{ packageVersionsUrl }}">{{ 'Versions'|trans }}</a>
1111
</li>
1212
<li class="nav-item">
13-
<span class="nav-link disabled">{{ 'Dependants'|trans }}</span>
13+
{% set packageDependentsUrl = path('dashboard_packages_dependents', {packageName: package.name}) %}
14+
<a {% if currentPage == 'dependents' %}class="nav-link active" aria-current="page"{% else %}class="nav-link text-primary"{% endif%} href="{{ packageDependentsUrl }}">{{ 'Dependents'|trans }}</a>
1415
</li>
1516
<li class="nav-item">
16-
{% set packageVersionsUrl = path('dashboard_packages_statistics', {packageName: package.name}) %}
17-
<a {% if currentPage == 'statistics' %}class="nav-link active" aria-current="page"{% else %}class="nav-link text-primary"{% endif%} href="{{ packageVersionsUrl }}">{{ 'Statistics'|trans }}</a>
17+
{% set packageStatisticsUrl = path('dashboard_packages_statistics', {packageName: package.name}) %}
18+
<a {% if currentPage == 'statistics' %}class="nav-link active" aria-current="page"{% else %}class="nav-link text-primary"{% endif%} href="{{ packageStatisticsUrl }}">{{ 'Statistics'|trans }}</a>
1819
</li>
1920
</ul>

templates/dashboard/packages/package_info.html.twig

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@
4545
<div><a href="{{ version.source.url }}">Source</a></div>
4646
{% endif %}
4747
<div>Installs: {{ package.installations.total }}</div>
48+
{% set packageDependentsUrl = path('dashboard_packages_dependents', {packageName: package.name}) %}
49+
<div><a href="{{ packageDependentsUrl }}">{{ 'Dependents'|trans }}</a>: {{ dependentCount }}</div>
50+
{% set packageSuggestersUrl = path('dashboard_packages_suggesters', {packageName: package.name}) %}
51+
<div><a href="{{ packageSuggestersUrl }}">{{ 'Suggesters'|trans }}</a>: {{ suggesterCount }}</div>
52+
{% set packageImplementationsUrl = path('dashboard_packages_implementations', {packageName: package.name}) %}
53+
<div><a href="{{ packageImplementationsUrl }}">{{ 'Implementations'|trans }}</a>: {{ implementationCount }}</div>
54+
{% set packageProvidersUrl = path('dashboard_packages_providers', {packageName: package.name}) %}
55+
<div><a href="{{ packageProvidersUrl }}">{{ 'Providers'|trans }}</a>: {{ providerCount }}</div>
4856
{% if version.license %}
4957
<div>License: {{ version.license|join(', ') }}</div>
5058
{% else %}
@@ -64,7 +72,7 @@
6472
<div class="row border-bottom pb-3 mb-3">
6573
{{ _self.linkBlock("requires", version.require) }}
6674
{{ _self.linkBlock("requires (dev)", version.devRequire) }}
67-
{{ _self.linkBlock("provides", version.provide) }}
75+
{{ _self.provideBlock("provides", version.provide) }}
6876
{{ _self.linkBlock("suggests", version.suggest) }}
6977
{{ _self.linkBlock("conflicts", version.conflict) }}
7078
{{ _self.linkBlock("replaces", version.replace) }}
@@ -94,3 +102,23 @@
94102
</ul>
95103
</div>
96104
{% endmacro %}
105+
106+
{% macro provideBlock(title, links) %}
107+
<div class="col-md-6 col-lg-4">
108+
<div class="h5">{{ title }}</div>
109+
110+
<ul>
111+
{% for link in links %}
112+
{% if link.linkedPackageName is existing_package %}
113+
<li><a href="{{ path('dashboard_packages_info', {packageName: link.linkedPackageName}) }}">{{ link.linkedPackageName }}</a>: {{ link.linkedVersionConstraint }}</li>
114+
{% elseif link.implementation and link.implementedPackageName is existing_package %}
115+
<li><a href="{{ path('dashboard_packages_info', {packageName: link.implementedPackageName}) }}">{{ link.linkedPackageName }}</a>: {{ link.linkedVersionConstraint }}</li>
116+
{% else %}
117+
<li>{{ link.linkedPackageName }}: {{ link.linkedVersionConstraint }}</li>
118+
{% endif %}
119+
{% else %}
120+
<li>None</li>
121+
{% endfor %}
122+
</ul>
123+
</div>
124+
{% endmacro %}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% extends 'dashboard/packages/package_base.html.twig' %}
2+
3+
{% block page_title %}{{ package.name }} <small>{{ title|trans }}</small>{% endblock %}
4+
5+
{% block page_content %}
6+
{% include 'dashboard/packages/_package_header.html.twig' with {currentPage: title|lower} %}
7+
8+
{{ include('dashboard/packages/_package_list.html.twig', {packages: packageLinks|map(packageLink => packageLink.package), paginator: paginator}, with_context: false) }}
9+
{% endblock %}

tests/FunctionalTests/Controller/Dashboard/DashboardPackagesInfoControllerTest.php

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,94 @@
22

33
namespace CodedMonkey\Dirigent\Tests\FunctionalTests\Controller\Dashboard;
44

5+
use CodedMonkey\Dirigent\Doctrine\Entity\User;
56
use CodedMonkey\Dirigent\Doctrine\Repository\PackageRepository;
7+
use CodedMonkey\Dirigent\Doctrine\Repository\UserRepository;
68
use CodedMonkey\Dirigent\Tests\FunctionalTests\WebTestCaseTrait;
79
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
810

911
class DashboardPackagesInfoControllerTest extends WebTestCase
1012
{
1113
use WebTestCaseTrait;
1214

15+
public function testDependents(): void
16+
{
17+
$client = static::createClient();
18+
19+
/** @var UserRepository $userRepository */
20+
$userRepository = $client->getContainer()->get(UserRepository::class);
21+
22+
/** @var User $user */
23+
$user = $userRepository->findOneByUsername('user');
24+
$client->loginUser($user);
25+
26+
$client->request('GET', '/packages/psr/log/dependents');
27+
28+
$this->assertResponseStatusCodeSame(200);
29+
30+
$this->assertAnySelectorTextSame('h1 small', 'Dependents');
31+
}
32+
33+
public function testImplementations(): void
34+
{
35+
$client = static::createClient();
36+
37+
/** @var UserRepository $userRepository */
38+
$userRepository = $client->getContainer()->get(UserRepository::class);
39+
40+
/** @var User $user */
41+
$user = $userRepository->findOneByUsername('user');
42+
$client->loginUser($user);
43+
44+
$client->request('GET', '/packages/psr/log/implementations');
45+
46+
$this->assertResponseStatusCodeSame(200);
47+
48+
$this->assertAnySelectorTextSame('h1 small', 'Implementations');
49+
}
50+
51+
public function testProviders(): void
52+
{
53+
$client = static::createClient();
54+
55+
/** @var UserRepository $userRepository */
56+
$userRepository = $client->getContainer()->get(UserRepository::class);
57+
58+
/** @var User $user */
59+
$user = $userRepository->findOneByUsername('user');
60+
$client->loginUser($user);
61+
62+
$client->request('GET', '/packages/psr/log/providers');
63+
64+
$this->assertResponseStatusCodeSame(200);
65+
66+
$this->assertAnySelectorTextSame('h1 small', 'Providers');
67+
}
68+
69+
public function testSuggesters(): void
70+
{
71+
$client = static::createClient();
72+
73+
/** @var UserRepository $userRepository */
74+
$userRepository = $client->getContainer()->get(UserRepository::class);
75+
76+
/** @var User $user */
77+
$user = $userRepository->findOneByUsername('user');
78+
$client->loginUser($user);
79+
80+
$client->request('GET', '/packages/psr/log/suggesters');
81+
82+
$this->assertResponseStatusCodeSame(200);
83+
84+
$this->assertAnySelectorTextSame('h1 small', 'Suggesters');
85+
}
86+
1387
public function testStatistics(): void
1488
{
1589
$client = static::createClient();
1690
$this->loginUser();
1791

18-
$client->request('GET', '/?routeName=dashboard_packages_statistics&routeParams[packageName]=psr/log');
92+
$client->request('GET', '/packages/psr/log/statistics');
1993

2094
$this->assertResponseStatusCodeSame(200);
2195

translations/messages.en.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ Account: Account
22
Administration: Administration
33
Credits: Credits
44
Dashboard: Dashboard
5-
Dependants: Dependants
5+
Dependents: Dependents
66
Documentation: Documentation
7+
Implementations: Implementations
78
Info: Info
89
Packages: Packages
910
Personal: Personal
11+
Providers: Providers
1012
Sign out: Sign out
1113
Statistics: Statistics
14+
Suggesters: Suggesters
1215
Usage: Usage
1316

1417
Access token: Access token

0 commit comments

Comments
 (0)