Skip to content

Commit 6bac677

Browse files
committed
Sort package versions
1 parent e0529c0 commit 6bac677

5 files changed

Lines changed: 183 additions & 25 deletions

File tree

src/Controller/Dashboard/DashboardPackagesInfoController.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,9 @@ public function versionInfo(string $packageName, string $packageVersion): Respon
6363
public function versions(string $packageName): Response
6464
{
6565
$package = $this->packageRepository->findOneBy(['name' => $packageName]);
66-
$versions = $package->getVersions()->toArray();
67-
68-
usort($versions, Package::class . '::sortVersions');
6966

7067
return $this->render('dashboard/packages/package_versions.html.twig', [
7168
'package' => $package,
72-
'versions' => $versions,
7369
]);
7470
}
7571

src/Doctrine/Entity/Package.php

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

55
use CodedMonkey\Dirigent\Doctrine\Repository\PackageRepository;
66
use CodedMonkey\Dirigent\Validator\UniquePackage;
7+
use Composer\Package\Version\VersionParser;
78
use Composer\Pcre\Preg;
89
use Doctrine\Common\Collections\ArrayCollection;
910
use Doctrine\Common\Collections\Collection;
@@ -91,6 +92,8 @@ class Package
9192
*/
9293
private array $cachedVersions;
9394

95+
private array $sortedVersions;
96+
9497
public function __construct()
9598
{
9699
$this->installations = new PackageInstallations($this);
@@ -368,18 +371,30 @@ public function getPrettyBrowsableRepositoryUrl(): ?string
368371
}
369372

370373
/**
371-
* Returns the default branch or latest version of the package.
374+
* @return Version[]
375+
*/
376+
public function getSortedVersions(): array
377+
{
378+
if (!isset($this->sortedVersions)) {
379+
$this->sortedVersions = $this->versions->toArray();
380+
381+
usort($this->sortedVersions, [static::class, 'sortVersions']);
382+
}
383+
384+
return $this->sortedVersions;
385+
}
386+
387+
/**
388+
* Returns the default branch or the latest version of the package.
372389
*/
373390
public function getDefaultVersion(): ?Version
374391
{
375-
$versions = $this->versions->toArray();
392+
$versions = $this->getSortedVersions();
376393

377394
if (!count($versions)) {
378395
return null;
379396
}
380397

381-
usort($versions, [static::class, 'sortVersions']);
382-
383398
$latestVersion = reset($versions);
384399
foreach ($versions as $version) {
385400
if ($version->isDefaultBranch()) {
@@ -391,18 +406,17 @@ public function getDefaultVersion(): ?Version
391406
}
392407

393408
/**
394-
* Returns the latest (numbered) version of the package, or the default version if no versions were found.
409+
* The latest (numbered) version of the package, or the default version if no versions were found.
395410
*/
396411
public function getLatestVersion(): ?Version
397412
{
398-
$versions = $this->versions->toArray();
413+
$versions = $this->getSortedVersions();
399414

400415
if (!count($versions)) {
401416
return null;
402417
}
403418

404-
usort($versions, [static::class, 'sortVersions']);
405-
419+
// Return the first non-development version
406420
foreach ($versions as $version) {
407421
if (!$version->isDevelopment()) {
408422
return $version;
@@ -412,6 +426,132 @@ public function getLatestVersion(): ?Version
412426
return $this->getDefaultVersion();
413427
}
414428

429+
/**
430+
* The latest version of each major version.
431+
*
432+
* @return Version[]
433+
*/
434+
public function getActiveVersions(): array
435+
{
436+
$activeVersions = [];
437+
$activePrereleaseVersions = [];
438+
439+
foreach ($this->getSortedVersions() as $version) {
440+
if ('stable' !== VersionParser::parseStability($version->getNormalizedVersion())) {
441+
continue;
442+
}
443+
444+
[$majorVersion, $minorVersion] = explode('.', $version->getNormalizedVersion());
445+
446+
if ('0' === $majorVersion) {
447+
$prereleaseVersion = "$majorVersion.$minorVersion";
448+
449+
$activePrereleaseVersions[$prereleaseVersion] ??= $version;
450+
if (version_compare($version->getNormalizedVersion(), $activePrereleaseVersions[$prereleaseVersion]->getNormalizedVersion(), '>')) {
451+
$activePrereleaseVersions[$prereleaseVersion] = $version;
452+
}
453+
454+
continue;
455+
}
456+
457+
$activeVersions[$majorVersion] ??= $version;
458+
if (version_compare($version->getNormalizedVersion(), $activeVersions[$majorVersion]->getNormalizedVersion(), '>')) {
459+
$activeVersions[$majorVersion] = $version;
460+
}
461+
}
462+
463+
$activeDevelopmentVersions = [];
464+
$activePrereleaseDevelopmentVersions = [];
465+
466+
// Find newer unstable releases of active versions
467+
foreach ($this->getSortedVersions() as $version) {
468+
if (in_array(VersionParser::parseStability($version->getNormalizedVersion()), ['stable', 'dev'], true)) {
469+
continue;
470+
}
471+
472+
[$majorVersion, $minorVersion] = explode('.', $version->getNormalizedVersion());
473+
474+
$developmentVersion = "$majorVersion.$minorVersion";
475+
476+
if ('0' === $majorVersion) {
477+
if (isset($activePrereleaseVersions[$developmentVersion]) && !version_compare($version->getNormalizedVersion(), $activePrereleaseVersions[$developmentVersion]->getNormalizedVersion(), '>')) {
478+
continue;
479+
}
480+
481+
$activePrereleaseDevelopmentVersions[$developmentVersion] ??= $version;
482+
if (version_compare($version->getNormalizedVersion(), $activePrereleaseDevelopmentVersions[$developmentVersion]->getNormalizedVersion(), '>')) {
483+
$activePrereleaseDevelopmentVersions[$developmentVersion] = $version;
484+
}
485+
486+
continue;
487+
}
488+
489+
if (isset($activeVersions[$majorVersion]) && !version_compare($version->getNormalizedVersion(), $activeVersions[$majorVersion]->getNormalizedVersion(), '>')) {
490+
continue;
491+
}
492+
493+
$activeDevelopmentVersions[$developmentVersion] ??= $version;
494+
if (version_compare($version->getNormalizedVersion(), $activeDevelopmentVersions[$developmentVersion]->getNormalizedVersion(), '>')) {
495+
$activeDevelopmentVersions[$version->getNormalizedVersion()] = $version;
496+
}
497+
}
498+
499+
$activeVersions = [...$activeVersions, ...$activeDevelopmentVersions];
500+
501+
if (count($activeVersions)) {
502+
usort($activeVersions, [static::class, 'sortVersions']);
503+
504+
return $activeVersions;
505+
}
506+
507+
// Only show pre-release versions (0.x.x) if no versions after 1.0.0 was found
508+
$activePrereleaseVersions = [...$activePrereleaseVersions, ...$activePrereleaseDevelopmentVersions];
509+
510+
usort($activePrereleaseVersions, [static::class, 'sortVersions']);
511+
512+
return $activePrereleaseVersions;
513+
}
514+
515+
/**
516+
* All non-development versions that are not part of the active versions.
517+
*
518+
* @return Version[]
519+
*/
520+
public function getHistoricalVersions(): array
521+
{
522+
$historicalVersions = array_filter($this->getSortedVersions(), static fn (Version $version) => !$version->isDevelopment());
523+
524+
return array_diff($historicalVersions, $this->getActiveVersions());
525+
}
526+
527+
/**
528+
* All development versions associated with a version number (2.0.x-dev, 0.1.x-dev).
529+
*
530+
* @return Version[]
531+
*/
532+
public function getDevVersions(): array
533+
{
534+
return array_filter($this->getSortedVersions(), static function (Version $version) {
535+
if (str_ends_with($version->getNormalizedVersion(), '.9999999-dev')) {
536+
return true;
537+
}
538+
539+
static $parser = new VersionParser();
540+
541+
return $version->hasVersionAlias() && str_ends_with($parser->normalize($version->getVersionAlias()), '.9999999-dev');
542+
});
543+
}
544+
545+
/**
546+
* All development versions associated with a branch (dev-main, dev-master, dev-develop).
547+
*
548+
* @return Version[]
549+
*/
550+
public function getDevBranchVersions(): array
551+
{
552+
return array_filter($this->getSortedVersions(), static fn (Version $version) => str_starts_with($version->getNormalizedVersion(), 'dev-'));
553+
}
554+
415555
public static function sortVersions(Version $a, Version $b): int
416556
{
417557
$aVersion = $a->getNormalizedVersion();

templates/dashboard/packages/package_statistics.html.twig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{{ include('dashboard/packages/_package_header.html.twig', {currentPage: 'statistics'}) }}
77

88
<div class="mb-3">
9-
<h2>{{ 'Installations'|trans }}</h2>
9+
<h2 class="h4">{{ 'Installations'|trans }}</h2>
1010
<div class="row">
1111
<div class="col-md-4">
1212
<div id="total_all" class="card mb-2">
@@ -36,7 +36,7 @@
3636
</div>
3737

3838
<div class="mb-3">
39-
<h2>{{ 'Daily installations'|trans }}</h2>
39+
<h2 class="h4">{{ 'Daily installations'|trans }}</h2>
4040
<div
4141
data-controller="chart"
4242
data-chart-data-value="{{ package.installations.data ? package.installations.data|json_encode|escape : '{}' }}"

templates/dashboard/packages/package_versions.html.twig

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,34 @@
55
{% block page_content %}
66
{{ include('dashboard/packages/_package_header.html.twig', {currentPage: 'versions'}) }}
77

8-
<div class="list-group list-group-flush mb-3">
9-
{% for version in versions %}
10-
{% set packageVersionInfoUrl = path('dashboard_packages_version_info', {packageName: package.name, packageVersion: version.version}) %}
11-
<a href="{{ packageVersionInfoUrl }}" class="list-group-item">
12-
<div class="d-flex justify-content-between">
8+
{{ _self.versionList('Latest versions', package.activeVersions) }}
9+
{{ _self.versionList('Development versions', package.devVersions) }}
10+
{{ _self.versionList('Historical versions', package.historicalVersions) }}
11+
{{ _self.versionList('Development branch versions', package.devBranchVersions) }}
12+
{% endblock %}
13+
14+
{% macro versionList(title, versions) %}
15+
{% if versions|length %}
16+
<h2 class="h4">{{ title|trans }}</h2>
17+
<div class="list-group list-group-flush border-bottom mb-3">
18+
{% for version in versions %}
19+
{{ _self.versionListItem(version) }}
20+
{% endfor %}
21+
</div>
22+
{% endif %}
23+
{% endmacro %}
24+
25+
{% macro versionListItem(version) %}
26+
{% set packageVersionInfoUrl = path('dashboard_packages_version_info', {packageName: version.package.name, packageVersion: version.version}) %}
27+
<a href="{{ packageVersionInfoUrl }}" class="list-group-item">
28+
<div class="d-flex justify-content-between">
1329
<span>
1430
{{ version.version }}
1531
{% if version.hasVersionAlias() %}
1632
/ {{ version.versionAlias }}
1733
{% endif %}
1834
</span>
19-
<span class="text-muted">{{ version.releasedAt|date }}</span>
20-
</div>
21-
</a>
22-
{% endfor %}
23-
</div>
24-
{% endblock %}
35+
<span class="text-muted">{{ version.releasedAt.format('Y-m-d H:i') }} UTC</span>
36+
</div>
37+
</a>
38+
{% endmacro %}

translations/messages.en.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,16 @@ License: License
5050
Providers: Providers
5151
Statistics: Statistics
5252
Suggesters: Suggesters
53+
54+
# Package labels (Statistics)
5355
Total installations: Total installations
5456

57+
# Package labels (Versions)
58+
Development branch versions: Development branch versions
59+
Development versions: Development versions
60+
Historical versions: Historical versions
61+
Latest versions: Latest versions
62+
5563
# Page actions
5664
Impersonate: Impersonate
5765
Move Down: Move down

0 commit comments

Comments
 (0)