Skip to content

Commit 82a1034

Browse files
authored
Merge pull request #11 from codedmonkey/revision-history
Add revision history UI
2 parents c8c88c6 + c0662c4 commit 82a1034

10 files changed

Lines changed: 260 additions & 71 deletions

File tree

assets/stylesheets/dashboard-theme.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,8 @@ body.ea-dark-scheme img.img-light {
7777
body:not(.ea-dark-scheme) img.img-dark {
7878
display: none;
7979
}
80+
81+
.text-monospace {
82+
font-family: var(--font-family-monospace);
83+
font-size: 13px;
84+
}

src/Controller/Dashboard/DashboardPackagesInfoController.php

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use CodedMonkey\Dirigent\Attribute\IsGrantedAccess;
66
use CodedMonkey\Dirigent\Attribute\MapPackage;
7-
use CodedMonkey\Dirigent\Doctrine\Entity\Metadata;
87
use CodedMonkey\Dirigent\Doctrine\Entity\Package;
98
use CodedMonkey\Dirigent\Doctrine\Entity\PackageProvideLink;
109
use CodedMonkey\Dirigent\Doctrine\Entity\PackageRequireLink;
@@ -23,6 +22,7 @@ class DashboardPackagesInfoController extends AbstractController
2322
{
2423
public function __construct(
2524
private readonly EntityManagerInterface $entityManager,
25+
private readonly MetadataRepository $metadataRepository,
2626
) {
2727
}
2828

@@ -31,24 +31,40 @@ public function __construct(
3131
*/
3232
#[Route('/packages/{package}', name: 'dashboard_packages_info', requirements: ['package' => MapPackage::PACKAGE_REGEX])]
3333
#[IsGrantedAccess]
34-
public function info(#[MapPackage] Package $package): Response
34+
public function info(Request $request, #[MapPackage] Package $package): Response
3535
{
3636
$version = $package->getLatestVersion();
3737

3838
if (!$version) {
3939
return $this->redirectToRoute('dashboard_packages_versions', ['package' => $package->getName()]);
4040
}
4141

42-
return $this->versionInfo($package, $version);
42+
return $this->versionInfo($request, $package, $version, latest: true);
4343
}
4444

4545
#[Route('/packages/{package}/versions/{version}', name: 'dashboard_packages_version_info', requirements: ['package' => MapPackage::PACKAGE_REGEX, 'version' => '.*'])]
4646
#[IsGrantedAccess]
47-
public function versionInfo(#[MapPackage] Package $package, #[MapPackage] Version $version): Response
48-
{
49-
/** @var MetadataRepository $metadataRepository */
50-
$metadataRepository = $this->entityManager->getRepository(Metadata::class);
51-
$metadataRepository->fetchMetadataCollections($version->getCurrentMetadata());
47+
public function versionInfo(
48+
Request $request,
49+
#[MapPackage] Package $package,
50+
#[MapPackage] Version $version,
51+
bool $latest = false,
52+
): Response {
53+
$metadata = $version->getCurrentMetadata();
54+
$revision = $request->query->getInt('revision');
55+
56+
// Only check for a revision number if the route is for a specific version: $latest !== true
57+
if ($revision > 0 && !$latest) {
58+
$metadata = $this->metadataRepository->findOneBy(['version' => $version, 'revision' => $revision]);
59+
60+
if (null === $metadata) {
61+
throw $this->createNotFoundException('The revision does not exist.');
62+
}
63+
}
64+
65+
$this->metadataRepository->fetchMetadataCollections($metadata);
66+
67+
$metadataCount = $this->metadataRepository->getMetadataCountForVersion($version);
5268

5369
$dependentCount = $this->entityManager->getRepository(PackageRequireLink::class)->count(['linkedPackageName' => $package->getName()]);
5470
$implementationCount = $this->entityManager->getRepository(PackageProvideLink::class)->count(['linkedPackageName' => $package->getName(), 'implementation' => true]);
@@ -58,21 +74,39 @@ public function versionInfo(#[MapPackage] Package $package, #[MapPackage] Versio
5874
return $this->render('dashboard/packages/package_info.html.twig', [
5975
'package' => $package,
6076
'version' => $version,
61-
'metadata' => $version->getCurrentMetadata(),
77+
'metadata' => $metadata,
6278

6379
'dependentCount' => $dependentCount,
6480
'implementationCount' => $implementationCount,
81+
'metadataCount' => $metadataCount,
6582
'providerCount' => $providerCount,
6683
'suggesterCount' => $suggesterCount,
6784
]);
6885
}
6986

87+
#[Route('/packages/{package}/revisions/{version}', name: 'dashboard_packages_version_metadata_list', requirements: ['package' => MapPackage::PACKAGE_REGEX, 'version' => '.*'])]
88+
#[IsGrantedAccess]
89+
public function versionMetadataList(
90+
#[MapPackage] Package $package,
91+
#[MapPackage] Version $version,
92+
): Response {
93+
$metadataCollection = $this->metadataRepository->getMetadataCollectionForVersion($version);
94+
95+
return $this->render('dashboard/packages/package_version_revisions.html.twig', [
96+
'package' => $package,
97+
'version' => $version,
98+
99+
'metadataCollection' => $metadataCollection,
100+
]);
101+
}
102+
70103
#[Route('/packages/{package}/versions', name: 'dashboard_packages_versions', requirements: ['package' => MapPackage::PACKAGE_REGEX])]
71104
#[IsGrantedAccess]
72105
public function versions(#[MapPackage] Package $package): Response
73106
{
74107
return $this->render('dashboard/packages/package_versions.html.twig', [
75108
'package' => $package,
109+
'metadataCounts' => $this->metadataRepository->getMetadataCountsForPackage($package),
76110
]);
77111
}
78112

src/Doctrine/Entity/Metadata.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,16 @@ public function getKeywords(bool $raw = false): Collection
466466
return self::getOrderedCollection($this->keywords, $raw);
467467
}
468468

469+
public function isCurrentMetadata(): bool
470+
{
471+
return $this->version->hasCurrentMetadata() && $this === $this->version->getCurrentMetadata();
472+
}
473+
474+
public function getReference(): ?string
475+
{
476+
return $this->getSourceReference() ?? $this->getDistReference();
477+
}
478+
469479
public function hasSource(): bool
470480
{
471481
return null !== $this->source;

src/Doctrine/Repository/MetadataRepository.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
namespace CodedMonkey\Dirigent\Doctrine\Repository;
44

55
use CodedMonkey\Dirigent\Doctrine\Entity\Metadata;
6+
use CodedMonkey\Dirigent\Doctrine\Entity\Package;
7+
use CodedMonkey\Dirigent\Doctrine\Entity\Version;
68
use CodedMonkey\Dirigent\Entity\MetadataLinkType;
79
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
10+
use Doctrine\Common\Collections\Order;
811
use Doctrine\Persistence\ManagerRegistry;
912

1013
/**
@@ -40,6 +43,43 @@ public function remove(Metadata $entity, bool $flush = false): void
4043
}
4144
}
4245

46+
public function getMetadataCountForVersion(Version $version): int
47+
{
48+
return $this->count(['version' => $version]);
49+
}
50+
51+
public function getMetadataCollectionForVersion(Version $version): array
52+
{
53+
return $this->findBy(
54+
['version' => $version],
55+
['revision' => Order::Descending->value],
56+
);
57+
}
58+
59+
/**
60+
* Returns a map of version ID => metadata count for all versions of the given package.
61+
*
62+
* @return array<int, int>
63+
*/
64+
public function getMetadataCountsForPackage(Package $package): array
65+
{
66+
$rows = $this->createQueryBuilder('metadata')
67+
->select('IDENTITY(metadata.version) as version_id, COUNT(metadata.id) as revision_count')
68+
->join('metadata.version', 'version')
69+
->where('version.package = :package')
70+
->groupBy('metadata.version')
71+
->setParameter('package', $package)
72+
->getQuery()
73+
->getResult();
74+
75+
$counts = [];
76+
foreach ($rows as $row) {
77+
$counts[(int) $row['version_id']] = (int) $row['revision_count'];
78+
}
79+
80+
return $counts;
81+
}
82+
4383
/**
4484
* Initializes all link and keyword collections for the given metadata.
4585
*/

templates/dashboard/packages/_package_header.html.twig

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
{% set attrActive = 'class="nav-link active" aria-current="page"' %}
33
{% set attr = 'class="nav-link text-primary"' %}
44

5-
<ul class="nav nav-pills nav-justified d-grid d-md-flex gap-1 pb-3 mb-3 border-bottom">
5+
<ul class="nav nav-pills nav-justified d-grid d-md-flex gap-1 pb-2 mb-3 border-bottom">
66
<li class="nav-item">
77
{% if package.versions|length > 0 %}
8-
{% set packageInfoUrl = path('dashboard_packages_info', {package: package.name}) %}
8+
{% set packageInfoUrl = version is defined
9+
? path('dashboard_packages_version_info', {package: package.name, version: version.name})
10+
: path('dashboard_packages_info', {package: package.name})
11+
%}
912
<a {{ (currentPage == 'info' ? attrActive : attr)|raw }} href="{{ packageInfoUrl }}">{{ 'Info'|trans }}</a>
1013
{% else %}
1114
<span class="nav-link disabled">{{ 'Info'|trans }}</span>

templates/dashboard/packages/package_info.html.twig

Lines changed: 85 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -100,66 +100,99 @@
100100
{% endif %}
101101
</div>
102102

103-
<div class="row border-bottom pb-3 mb-3">
104-
{{ _self.linkBlock('Requires', metadata.requireLinks) }}
105-
{{ _self.linkBlock('Requires (dev)', metadata.devRequireLinks) }}
106-
{{ _self.provideBlock(metadata.provideLinks) }}
107-
{{ _self.linkBlock('Suggests', metadata.suggestLinks) }}
108-
{{ _self.linkBlock('Conflicts', metadata.conflictLinks) }}
109-
{{ _self.linkBlock('Replaces', metadata.replaceLinks) }}
103+
<div class="border-bottom mb-3">
104+
<div class="row g-0">
105+
{{ _self.linkBlock('Requires', metadata.requireLinks) }}
106+
{{ _self.linkBlock('Requires (dev)', metadata.devRequireLinks) }}
107+
{{ _self.provideBlock(metadata.provideLinks) }}
108+
{{ _self.linkBlock('Suggests', metadata.suggestLinks) }}
109+
{{ _self.linkBlock('Conflicts', metadata.conflictLinks) }}
110+
{{ _self.linkBlock('Replaces', metadata.replaceLinks) }}
111+
</div>
110112
</div>
111113

112-
<div class="row border-bottom pb-3 mb-3">
113-
<div class="col-md-6">
114-
{% if metadata.hasSource() %}
115-
<div class="h5">
116-
{{ 'Source'|trans }}
117-
<span class="badge text-bg-secondary">{{ metadata.sourceType }}</span>
114+
<div class="border-bottom pb-3 mb-3">
115+
{% if metadataCount > 1 %}
116+
<div class="bg-body-tertiary border border-warning-subtle px-3 py-2 mb-3 rounded">
117+
<p>
118+
{{ 'Multiple revisions found for version %version% of package %package%'|trans({
119+
'%package%': "<strong>#{package.name|escape}</strong>",
120+
'%version%': "<strong>#{version.name|escape}</strong>",
121+
})|raw }}
122+
</p>
123+
{% if not metadata.isCurrentMetadata %}
124+
<p class="text-warning">
125+
<strong>
126+
{{ "You're viewing an inactive revision of this package"|trans }}
127+
</strong>
128+
</p>
129+
{% endif %}
130+
<div class="btn-toolbar gap-1">
131+
<a class="btn btn-sm btn-secondary" href="{{ path('dashboard_packages_version_metadata_list', {package: package.name, version: version.name}) }}">
132+
<span class="fa-solid fa-list me-1" aria-hidden="true"></span>
133+
<span class="btn-label">{{ 'Revisions'|trans }}</span>
134+
</a>
135+
{% if not metadata.isCurrentMetadata %}
136+
<a class="btn btn-sm btn-secondary" href="{{ path('dashboard_packages_version_info', {package: package.name, version: version.name}) }}">
137+
<span class="fa-solid fa-code-commit me-1" aria-hidden="true"></span>
138+
<span class="btn-label">{{ 'Current revision'|trans }}</span>
139+
</a>
140+
{% endif %}
118141
</div>
142+
</div>
143+
{% endif %}
144+
<div class="row g-0">
145+
<div class="col-md-6">
146+
{% if metadata.hasSource() %}
147+
<div class="h5">
148+
{{ 'Source'|trans }}
149+
<span class="badge text-bg-secondary">{{ metadata.sourceType }}</span>
150+
</div>
119151

120-
<div class="mb-2">
121-
<code>{{ metadata.sourceUrl }}</code>
122-
</div>
152+
<div class="mb-2">
153+
<code>{{ metadata.sourceUrl }}</code>
154+
</div>
123155

124-
<div class="mb-2">
125-
{{ 'Reference'|trans }}: <code>{{ metadata.sourceReference }}</code>
126-
</div>
156+
<div class="mb-2">
157+
{{ 'Reference'|trans }}: <code>{{ metadata.sourceReference }}</code>
158+
</div>
127159

128-
<div class="mb-2">
129-
{% set browseUrl = metadata.browsableRepositoryUrl %}
130-
{% if browseUrl %}
131-
<a class="btn btn-sm btn-secondary" href="{{ browseUrl }}">
132-
<span class="fa-solid fa-code me-1" aria-hidden="true"></span>
133-
{{ 'Browse code'|trans }}
134-
</a>
135-
{% endif %}
136-
</div>
137-
{% else %}
138-
<div class="h5">{{ 'Source'|trans }}</div>
139-
-
140-
{% endif %}
141-
</div>
142-
<div class="col-md-6">
143-
{% if metadata.hasDist() %}
144-
<div class="h5">
145-
{{ 'Distribution'|trans }}
146-
<span class="badge text-bg-secondary">{{ metadata.distType }}</span>
147-
</div>
160+
<div>
161+
{% set browseUrl = metadata.browsableRepositoryUrl %}
162+
{% if browseUrl %}
163+
<a class="btn btn-sm btn-secondary" href="{{ browseUrl }}">
164+
<span class="fa-solid fa-code me-1" aria-hidden="true"></span>
165+
<span class="btn-label">{{ 'Browse code'|trans }}</span>
166+
</a>
167+
{% endif %}
168+
</div>
169+
{% else %}
170+
<div class="h5">{{ 'Source'|trans }}</div>
171+
-
172+
{% endif %}
173+
</div>
174+
<div class="col-md-6">
175+
{% if metadata.hasDist() %}
176+
<div class="h5">
177+
{{ 'Distribution'|trans }}
178+
<span class="badge text-bg-secondary">{{ metadata.distType }}</span>
179+
</div>
148180

149-
<div class="mb-2">
150-
{{ 'Reference'|trans }}: <code>{{ metadata.distReference }}</code>
151-
</div>
181+
<div class="mb-2">
182+
{{ 'Reference'|trans }}: <code>{{ metadata.distReference }}</code>
183+
</div>
152184

153-
<div class="mb-2">
154-
<a class="btn btn-sm btn-secondary" href="{{ metadata.distUrl }}" download>
155-
<span class="fa-solid fa-download me-1" aria-hidden="true"></span>
156-
{{ 'Download' }}
157-
</a>
158-
</div>
159-
{% else %}
160-
<div class="h5">{{ 'Distribution'|trans }}</div>
161-
-
162-
{% endif %}
185+
<div>
186+
<a class="btn btn-sm btn-secondary" href="{{ metadata.distUrl }}" download>
187+
<span class="fa-solid fa-download me-1" aria-hidden="true"></span>
188+
<span class="btn-label">{{ 'Download' }}</span>
189+
</a>
190+
</div>
191+
{% else %}
192+
<div class="h5">{{ 'Distribution'|trans }}</div>
193+
-
194+
{% endif %}
195+
</div>
163196
</div>
164197
</div>
165198

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{% extends 'dashboard/packages/package_base.html.twig' %}
2+
3+
{% block page_title %}
4+
{%- apply spaceless -%}
5+
{{ package.name }}
6+
<small>{{ version.extendedName }}</small>
7+
{%- endapply -%}
8+
{% endblock %}
9+
10+
{% block page_content %}
11+
{{ include('dashboard/packages/_package_header.html.twig') }}
12+
13+
<h2 class="h4">{{ 'Revisions'|trans }}</h2>
14+
15+
<div class="list-group list-group-flush border-bottom mb-3">
16+
{% for metadata in metadataCollection %}
17+
{% set infoUrl = metadata.isCurrentMetadata
18+
? path('dashboard_packages_version_info', {package: package.name, version: version.name})
19+
: path('dashboard_packages_version_info', {package: package.name, version: version.name, revision: metadata.revision})
20+
%}
21+
<a href="{{ infoUrl }}" class="list-group-item">
22+
<div class="d-flex justify-content-between">
23+
<div>
24+
<span>{{ 'Revision'|trans }} {{ metadata.revision }}: <span class="text-monospace">{{ metadata.reference }}</span></span>
25+
</div>
26+
<span class="text-muted">{{ metadata.releasedAt ? metadata.releasedAt.format('Y-m-d') }}</span>
27+
</div>
28+
<div>
29+
<span>{{ 'Indexed on %date%'|trans({'%date%': metadata.createdAt.format('Y-m-d')}) }}</span>
30+
{% if metadata.isCurrentMetadata %}
31+
<span class="badge text-bg-secondary ms-1">{{ 'Current revision'|trans }}</span>
32+
{% endif %}
33+
</div>
34+
</a>
35+
{% endfor %}
36+
</div>
37+
{% endblock %}

0 commit comments

Comments
 (0)