Skip to content

Commit 1c09fa9

Browse files
committed
Create package link pages
1 parent 11733cc commit 1c09fa9

File tree

10 files changed

+272
-5
lines changed

10 files changed

+272
-5
lines changed

src/Controller/Dashboard/DashboardPackagesInfoController.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,23 @@
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\Provider;
9+
use CodedMonkey\Dirigent\Doctrine\Entity\Suggester;
710
use CodedMonkey\Dirigent\Doctrine\Repository\PackageRepository;
11+
use CodedMonkey\Dirigent\EasyAdmin\PackagePaginator;
812
use Composer\Semver\VersionParser;
13+
use Doctrine\ORM\EntityManagerInterface;
914
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
15+
use Symfony\Component\HttpFoundation\Request;
1016
use Symfony\Component\HttpFoundation\Response;
1117
use Symfony\Component\Routing\Attribute\Route;
1218

1319
class DashboardPackagesInfoController extends AbstractController
1420
{
1521
public function __construct(
22+
private readonly EntityManagerInterface $entityManager,
1623
private readonly PackageRepository $packageRepository,
1724
) {
1825
}
@@ -34,10 +41,20 @@ public function info(string $packageName, ?string $packageVersion = null): Respo
3441
$version = $package->getLatestVersion();
3542
}
3643

44+
$dependentCount = $this->entityManager->getRepository(Dependent::class)->count(['dependentPackageName' => $package->getName()]);
45+
$implementationCount = $this->entityManager->getRepository(Provider::class)->count(['providedPackageName' => $package->getName(), 'implementation' => true]);
46+
$providerCount = $this->entityManager->getRepository(Provider::class)->count(['providedPackageName' => $package->getName(), 'implementation' => false]);
47+
$suggesterCount = $this->entityManager->getRepository(Suggester::class)->count(['suggestedPackageName' => $package->getName()]);
48+
3749
return $this->render('dashboard/packages/package_info.html.twig', [
3850
'package' => $package,
3951
'latestVersion' => $latestVersion,
4052
'version' => $version,
53+
54+
'dependentCount' => $dependentCount,
55+
'implementationCount' => $implementationCount,
56+
'providerCount' => $providerCount,
57+
'suggesterCount' => $suggesterCount,
4158
]);
4259
}
4360

@@ -56,6 +73,104 @@ public function versions(string $packageName): Response
5673
]);
5774
}
5875

76+
#[Route('/dashboard/packages/dependents/{packageName}', name: 'dashboard_packages_dependents', requirements: ['packageName' => '[a-z0-9_.-]+/[a-z0-9_.-]+'])]
77+
#[IsGrantedAccess]
78+
public function dependents(Request $request, string $packageName): Response
79+
{
80+
$package = $this->packageRepository->findOneBy(['name' => $packageName]);
81+
82+
$dependentRepository = $this->entityManager->getRepository(Dependent::class);
83+
$queryBuilder = $dependentRepository->createQueryBuilder('dependent');
84+
$queryBuilder
85+
->leftJoin('dependent.package', 'package')
86+
->andWhere('dependent.dependentPackageName = :packageName')
87+
->setParameter('packageName', $package->getName())
88+
->addOrderBy('package.name', 'ASC');
89+
90+
$paginator = PackagePaginator::fromRequest($request, $queryBuilder, $this->container->get('router'));
91+
$dependents = $paginator->getResults();
92+
93+
return $this->render('dashboard/packages/package_dependents.html.twig', [
94+
'package' => $package,
95+
'dependents' => $dependents,
96+
'paginator' => $paginator,
97+
]);
98+
}
99+
100+
#[Route('/dashboard/packages/implementations/{packageName}', name: 'dashboard_packages_implementations', requirements: ['packageName' => '[a-z0-9_.-]+/[a-z0-9_.-]+'])]
101+
#[IsGrantedAccess]
102+
public function implementations(Request $request, string $packageName): Response
103+
{
104+
$package = $this->packageRepository->findOneBy(['name' => $packageName]);
105+
106+
$providerRepository = $this->entityManager->getRepository(Provider::class);
107+
$queryBuilder = $providerRepository->createQueryBuilder('provider');
108+
$queryBuilder
109+
->leftJoin('provider.package', 'package')
110+
->andWhere('provider.providedPackageName = :packageName')
111+
->andWhere('provider.implementation = true')
112+
->setParameter('packageName', $package->getName())
113+
->addOrderBy('package.name', 'ASC');
114+
115+
$paginator = PackagePaginator::fromRequest($request, $queryBuilder, $this->container->get('router'));
116+
$providers = $paginator->getResults();
117+
118+
return $this->render('dashboard/packages/package_implementations.html.twig', [
119+
'package' => $package,
120+
'providers' => $providers,
121+
'paginator' => $paginator,
122+
]);
123+
}
124+
125+
#[Route('/dashboard/packages/providers/{packageName}', name: 'dashboard_packages_providers', requirements: ['packageName' => '[a-z0-9_.-]+/[a-z0-9_.-]+'])]
126+
#[IsGrantedAccess]
127+
public function providers(Request $request, string $packageName): Response
128+
{
129+
$package = $this->packageRepository->findOneBy(['name' => $packageName]);
130+
131+
$providerRepository = $this->entityManager->getRepository(Provider::class);
132+
$queryBuilder = $providerRepository->createQueryBuilder('provider');
133+
$queryBuilder
134+
->leftJoin('provider.package', 'package')
135+
->andWhere('provider.providedPackageName = :packageName')
136+
->andWhere('provider.implementation = false')
137+
->setParameter('packageName', $package->getName())
138+
->addOrderBy('package.name', 'ASC');
139+
140+
$paginator = PackagePaginator::fromRequest($request, $queryBuilder, $this->container->get('router'));
141+
$providers = $paginator->getResults();
142+
143+
return $this->render('dashboard/packages/package_providers.html.twig', [
144+
'package' => $package,
145+
'providers' => $providers,
146+
'paginator' => $paginator,
147+
]);
148+
}
149+
150+
#[Route('/dashboard/packages/suggesters/{packageName}', name: 'dashboard_packages_suggesters', requirements: ['packageName' => '[a-z0-9_.-]+/[a-z0-9_.-]+'])]
151+
#[IsGrantedAccess]
152+
public function suggesters(Request $request, string $packageName): Response
153+
{
154+
$package = $this->packageRepository->findOneBy(['name' => $packageName]);
155+
156+
$suggesterRepository = $this->entityManager->getRepository(Suggester::class);
157+
$queryBuilder = $suggesterRepository->createQueryBuilder('suggester');
158+
$queryBuilder
159+
->leftJoin('suggester.package', 'package')
160+
->andWhere('suggester.suggestedPackageName = :packageName')
161+
->setParameter('packageName', $package->getName())
162+
->addOrderBy('package.name', 'ASC');
163+
164+
$paginator = PackagePaginator::fromRequest($request, $queryBuilder, $this->container->get('router'));
165+
$suggesters = $paginator->getResults();
166+
167+
return $this->render('dashboard/packages/package_suggesters.html.twig', [
168+
'package' => $package,
169+
'suggesters' => $suggesters,
170+
'paginator' => $paginator,
171+
]);
172+
}
173+
59174
#[Route('/dashboard/packages/statistics/{packageName}', name: 'dashboard_packages_statistics', requirements: ['packageName' => '[a-z0-9_.-]+/[a-z0-9_.-]+'])]
60175
#[IsGrantedAccess]
61176
public function statistics(string $packageName): Response

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>
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>{{ 'Dependents'|trans }}</small>{% endblock %}
4+
5+
{% block page_content %}
6+
{% include 'dashboard/packages/_package_header.html.twig' with {currentPage: 'dependents'} %}
7+
8+
{{ include('dashboard/packages/_package_list.html.twig', {packages: dependents|map(dependent => dependent.package), paginator: paginator}, with_context: false) }}
9+
{% endblock %}
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>{{ 'Implementations'|trans }}</small>{% endblock %}
4+
5+
{% block page_content %}
6+
{% include 'dashboard/packages/_package_header.html.twig' with {currentPage: 'implementations'} %}
7+
8+
{{ include('dashboard/packages/_package_list.html.twig', {packages: providers|map(provider => provider.package), paginator: paginator}, with_context: false) }}
9+
{% endblock %}

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>{{ 'Providers'|trans }}</small>{% endblock %}
4+
5+
{% block page_content %}
6+
{% include 'dashboard/packages/_package_header.html.twig' with {currentPage: 'providers'} %}
7+
8+
{{ include('dashboard/packages/_package_list.html.twig', {packages: providers|map(provider => provider.package), paginator: paginator}, with_context: false) }}
9+
{% endblock %}
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>{{ 'Suggesters'|trans }}</small>{% endblock %}
4+
5+
{% block page_content %}
6+
{% include 'dashboard/packages/_package_header.html.twig' %}
7+
8+
{{ include('dashboard/packages/_package_list.html.twig', {packages: suggesters|map(suggester => suggester.package), paginator: paginator}, with_context: false) }}
9+
{% endblock %}

tests/FunctionalTests/Controller/Dashboard/DashboardPackagesInfoControllerTest.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,88 @@
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', '/?routeName=dashboard_packages_dependents&routeParams[packageName]=psr/log');
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', '/?routeName=dashboard_packages_implementations&routeParams[packageName]=psr/log');
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', '/?routeName=dashboard_packages_providers&routeParams[packageName]=psr/log');
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', '/?routeName=dashboard_packages_suggesters&routeParams[packageName]=psr/log');
81+
82+
$this->assertResponseStatusCodeSame(200);
83+
84+
$this->assertAnySelectorTextSame('h1 small', 'Suggesters');
85+
}
86+
1387
public function testStatistics(): void
1488
{
1589
$client = static::createClient();

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)