From 9ba5f6467e33c4a476a2897f10aee3b530dd01ed Mon Sep 17 00:00:00 2001 From: Joshua Gigg Date: Thu, 11 Mar 2021 13:47:35 +0000 Subject: [PATCH 01/10] Basic dependency/dependant tracking --- src/Controller/OrganizationController.php | 15 ++ src/Entity/Organization/Package.php | 23 +++ src/Entity/Organization/Package/Link.php | 156 ++++++++++++++++++ src/Migrations/Version20210309201702.php | 39 +++++ src/Query/User/PackageQuery.php | 17 ++ .../User/PackageQuery/DbalPackageQuery.php | 114 +++++++++++++ .../ComposerPackageSynchronizer.php | 25 +++ .../organization/package/details.html.twig | 35 ++++ 8 files changed, 424 insertions(+) create mode 100644 src/Entity/Organization/Package/Link.php create mode 100644 src/Migrations/Version20210309201702.php diff --git a/src/Controller/OrganizationController.php b/src/Controller/OrganizationController.php index 7d5cae4e..c0206037 100644 --- a/src/Controller/OrganizationController.php +++ b/src/Controller/OrganizationController.php @@ -110,6 +110,19 @@ public function packageDetails(Organization $organization, PackageDetails $packa { $filter = Filter::fromRequest($request); + $packageLinks = $this->packageQuery->getLinks($package->id(), $organization->id()); + $dependants = $this->packageQuery->getDependantLinks($package->name(), $organization->id()); + + $groupedPackageLinks = []; + + foreach ($packageLinks as $packageLink) { + if (!isset($groupedPackageLinks[$packageLink->type()])) { + $groupedPackageLinks[$packageLink->type()] = []; + } + + $groupedPackageLinks[$packageLink->type()][] = $packageLink; + } + return $this->render('organization/package/details.html.twig', [ 'organization' => $organization, 'package' => $package, @@ -117,6 +130,8 @@ public function packageDetails(Organization $organization, PackageDetails $packa 'count' => $this->packageQuery->versionCount($package->id()), 'versions' => $this->packageQuery->getVersions($package->id(), $filter), 'installs' => $this->packageQuery->getInstalls($package->id(), 0), + 'packageLinks' => $groupedPackageLinks, + 'dependants' => $dependants, ]); } diff --git a/src/Entity/Organization/Package.php b/src/Entity/Organization/Package.php index 58406083..46f1b8f2 100644 --- a/src/Entity/Organization/Package.php +++ b/src/Entity/Organization/Package.php @@ -5,6 +5,7 @@ namespace Buddy\Repman\Entity\Organization; use Buddy\Repman\Entity\Organization; +use Buddy\Repman\Entity\Organization\Package\Link; use Buddy\Repman\Entity\Organization\Package\Version; use Buddy\Repman\Entity\User\OAuthToken; use Doctrine\Common\Collections\ArrayCollection; @@ -120,6 +121,12 @@ class Package */ private Collection $versions; + /** + * @var Collection|Link[] + * @ORM\OneToMany(targetEntity="Buddy\Repman\Entity\Organization\Package\Link", mappedBy="package", cascade={"persist"}, orphanRemoval=true) + */ + private Collection $links; + /** * @ORM\Column(type="integer") */ @@ -136,6 +143,7 @@ public function __construct(UuidInterface $id, string $type, string $url, array $this->metadata = $metadata; $this->keepLastReleases = $keepLastReleases; $this->versions = new ArrayCollection(); + $this->links = new ArrayCollection(); } public function id(): UuidInterface @@ -320,6 +328,21 @@ public function addOrUpdateVersion(Version $version): void $this->versions->add($version); } + /** + * @return Collection|Link[] + */ + public function links(): Collection + { + return $this->links; + } + + public function addLink(Link $link): void + { + $link->setPackage($this); + $link->setOrganization($this->organization); + $this->links->add($link); + } + public function removeVersion(Version $version): void { $this->versions->removeElement($version); diff --git a/src/Entity/Organization/Package/Link.php b/src/Entity/Organization/Package/Link.php new file mode 100644 index 00000000..14a78da6 --- /dev/null +++ b/src/Entity/Organization/Package/Link.php @@ -0,0 +1,156 @@ +id = $id; + $this->type = $type; + $this->target = $target; + $this->constraint = $constraint; + $this->packageId = $packageId; + $this->targetPackageId = $targetPackageId; + } + + public function id(): string + { + return $this->id; + } + + public function type(): string + { + return $this->type; + } + + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } + + public function target(): string + { + return $this->target; + } + + public function setTarget(string $target): self + { + $this->target = $target; + + return $this; + } + + public function constraint(): string + { + return $this->constraint; + } + + public function setConstraint(string $constraint): self + { + $this->constraint = $constraint; + + return $this; + } + + public function packageId(): ?string + { + return $this->packageId; + } + + public function targetPackageId(): ?string + { + return $this->targetPackageId; + } + + public function packageName(): ?string + { + return $this->packageName; + } + + public function setPackageName(?string $packageName): self + { + $this->packageName = $packageName; + + return $this; + } + + public function setOrganization(Organization $organization): void + { + if (isset($this->organization)) { + throw new \RuntimeException('You can not change link organization'); + } + $this->organization = $organization; + } + + public function setPackage(Package $package): void + { + if (isset($this->package)) { + throw new \RuntimeException('You can not change link package'); + } + $this->package = $package; + } +} diff --git a/src/Migrations/Version20210309201702.php b/src/Migrations/Version20210309201702.php new file mode 100644 index 00000000..6d45a947 --- /dev/null +++ b/src/Migrations/Version20210309201702.php @@ -0,0 +1,39 @@ +addSql('CREATE TABLE organization_package_link (id UUID NOT NULL, organization_id UUID NOT NULL, package_id UUID NOT NULL, target VARCHAR(255) NOT NULL, "constraint" VARCHAR(255) NOT NULL, type VARCHAR (255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX link_package_id_idx ON organization_package_link (package_id)'); + $this->addSql('CREATE INDEX link_target_idx ON organization_package_link (target)'); + $this->addSql('COMMENT ON COLUMN organization_package_link.id IS \'(DC2Type:uuid)\''); + $this->addSql('COMMENT ON COLUMN organization_package_link.package_id IS \'(DC2Type:uuid)\''); + $this->addSql('COMMENT ON COLUMN organization_package_link.organization_id IS \'(DC2Type:uuid)\''); + $this->addSql('ALTER TABLE organization_package_link ADD CONSTRAINT FK_CAKE4LIFE FOREIGN KEY (package_id) REFERENCES organization_package (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema) : void + { + // this down() migration is auto-generated, please modify it to your needs + + $this->addSql('DROP TABLE organization_package_link'); + } +} diff --git a/src/Query/User/PackageQuery.php b/src/Query/User/PackageQuery.php index 3267c620..deec3523 100644 --- a/src/Query/User/PackageQuery.php +++ b/src/Query/User/PackageQuery.php @@ -4,6 +4,7 @@ namespace Buddy\Repman\Query\User; +use Buddy\Repman\Entity\Organization\Package\Link; use Buddy\Repman\Query\Filter; use Buddy\Repman\Query\User\Model\Installs; use Buddy\Repman\Query\User\Model\Package; @@ -46,6 +47,22 @@ public function versionCount(string $packageId): int; */ public function getVersions(string $packageId, Filter $filter): array; + /** + * @return Link[] + */ + public function getLinks(string $packageId, string $organizationId): array; + + /** + * @return Link[] + */ + public function getDependantLinks(string $packageName, string $organizationId): array; + + /** + * @param Link[] $links + * @return array + */ + public function hydratePackageLinks(string $organizationId, array $links): array; + public function getInstalls(string $packageId, int $lastDays = 30, ?string $version = null): Installs; /** diff --git a/src/Query/User/PackageQuery/DbalPackageQuery.php b/src/Query/User/PackageQuery/DbalPackageQuery.php index 8e0ba081..7c1afc34 100644 --- a/src/Query/User/PackageQuery/DbalPackageQuery.php +++ b/src/Query/User/PackageQuery/DbalPackageQuery.php @@ -4,6 +4,7 @@ namespace Buddy\Repman\Query\User\PackageQuery; +use Buddy\Repman\Entity\Organization\Package\Link; use Buddy\Repman\Entity\Organization\Package\Version as VersionEntity; use Buddy\Repman\Query\Filter as BaseFilter; use Buddy\Repman\Query\User\Model\Installs; @@ -206,6 +207,119 @@ public function versionCount(string $packageId): int ); } + /** + * @return Link[] + */ + public function getDependantLinks(string $packageName, string $organizationId): array + { + return array_map(function (array $data): Link { + return (new Link( + $data['id'], + $data['type'], + $data['target'], + $data['constraint'], + $data['package_id'] + )) + ->setPackageName($data['name']); + }, $this->connection->fetchAllAssociative( + 'SELECT + l.id, + l.type, + l.target, + l.constraint, + p.id as package_id, + p.name + FROM organization_package_link l + JOIN organization_package p ON (p.id = l.package_id AND p.organization_id = l.organization_id) + WHERE l.target = :package_name + AND l.organization_id = :organization_id', [ + ':package_name' => $packageName, + ':organization_id' => $organizationId, + ])); + } + + /** + * @return Link[] + */ + public function getLinks(string $packageId, string $organizationId): array + { + return array_map(function (array $data): Link { + return new Link( + $data['id'], + $data['type'], + $data['target'], + $data['constraint'], + $data['package_id'], + $data['target_package_id'] + ); + }, $this->connection->fetchAllAssociative( + 'SELECT + l.id, + l.type, + l.target, + l.constraint, + l.package_id, + p.id as target_package_id + FROM organization_package_link l + LEFT JOIN organization_package p ON (p.name = l.target AND p.organization_id = :organization_id) + WHERE package_id = :package_id', [ + ':organization_id' => $organizationId, + ':package_id' => $packageId, + ])); + } + + /** + * @param Link[] $links + * @return array + */ + public function hydratePackageLinks(string $organizationId, array $links): array + { + // TODO: Implement hydratePackageLinks() method. + $linkNames = array_map(fn(Link $link) => $link->target(), $links); + + $data = $this->connection->fetchAllAssociative( + 'SELECT + id, + organization_id, + type, + repository_url, + name, + latest_released_version, + latest_release_date, + description, + last_sync_at, + last_sync_error, + webhook_created_at, + webhook_created_error, + last_scan_date, + last_scan_status, + last_scan_result, + keep_last_releases + FROM organization_package + WHERE organization_id = ? + AND name IN (?) + GROUP BY id', + [ + $organizationId, + $linkNames, + ], + [ + \PDO::PARAM_STR, + Connection::PARAM_STR_ARRAY, + ] + ); + + $items = []; + + foreach ($data as $row) { + $package = $this->hydratePackageDetails($row); + + $items[$package->name()] = $package; + } + + return $items; + } + /** * @return Version[] */ diff --git a/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php b/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php index 720c5c96..85466782 100644 --- a/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php +++ b/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php @@ -149,6 +149,31 @@ public function synchronize(Package $package): void if ($latest->getVersion() === $version['version']) { $this->readmeExtractor->extractReadme($package, $dist); + + // @todo Loop around all the types without copy/pasting so much + + // Set the version links + foreach ($latest->getRequires() as $require) { + $package->addLink( + new Package\Link( + Uuid::uuid4()->toString(), + 'require', + $require->getTarget(), + $require->getPrettyConstraint(), + ) + ); + } + + foreach ($latest->getDevRequires() as $require) { + $package->addLink( + new Package\Link( + Uuid::uuid4()->toString(), + 'devRequire', + $require->getTarget(), + $require->getPrettyConstraint(), + ) + ); + } } $package->addOrUpdateVersion( diff --git a/templates/organization/package/details.html.twig b/templates/organization/package/details.html.twig index 5d6f302f..bdbd4cc2 100644 --- a/templates/organization/package/details.html.twig +++ b/templates/organization/package/details.html.twig @@ -146,6 +146,41 @@ {% endif %} + {% if packageLinks|length > 0 %} +
+ +

Dependencies

+ + {% for type,links in packageLinks %} + {{ type }} + {% for link in links %} +
  • + {% if link.targetPackageId %} + + {{ link.target }}: {{ link.constraint }} + + {% else %} + {{ link.target }}: {{ link.constraint }} + {% endif %} +
  • + {% endfor %} + {% endfor %} + {% endif %} + + {% if dependants|length > 0 %} +
    + +

    Dependants

    + + {% for link in dependants %} +
  • + + {{ link.packageName }}: {{ link.constraint }} + +
  • + {% endfor %} + {% endif %} + {% if package.readme() %}
    From 69e68380a1d70a4228ca50ed227698ec9d1b10c9 Mon Sep 17 00:00:00 2001 From: Joshua Gigg Date: Tue, 16 Mar 2021 21:15:19 +0000 Subject: [PATCH 02/10] Add other link types to test a design --- src/Query/User/PackageQuery.php | 6 --- .../User/PackageQuery/DbalPackageQuery.php | 52 ------------------- .../ComposerPackageSynchronizer.php | 37 +++++++------ .../organization/package/details.html.twig | 34 ++++++------ 4 files changed, 40 insertions(+), 89 deletions(-) diff --git a/src/Query/User/PackageQuery.php b/src/Query/User/PackageQuery.php index deec3523..d310ddd1 100644 --- a/src/Query/User/PackageQuery.php +++ b/src/Query/User/PackageQuery.php @@ -57,12 +57,6 @@ public function getLinks(string $packageId, string $organizationId): array; */ public function getDependantLinks(string $packageName, string $organizationId): array; - /** - * @param Link[] $links - * @return array - */ - public function hydratePackageLinks(string $organizationId, array $links): array; - public function getInstalls(string $packageId, int $lastDays = 30, ?string $version = null): Installs; /** diff --git a/src/Query/User/PackageQuery/DbalPackageQuery.php b/src/Query/User/PackageQuery/DbalPackageQuery.php index 7c1afc34..f81b43f9 100644 --- a/src/Query/User/PackageQuery/DbalPackageQuery.php +++ b/src/Query/User/PackageQuery/DbalPackageQuery.php @@ -268,58 +268,6 @@ public function getLinks(string $packageId, string $organizationId): array ])); } - /** - * @param Link[] $links - * @return array - */ - public function hydratePackageLinks(string $organizationId, array $links): array - { - // TODO: Implement hydratePackageLinks() method. - $linkNames = array_map(fn(Link $link) => $link->target(), $links); - - $data = $this->connection->fetchAllAssociative( - 'SELECT - id, - organization_id, - type, - repository_url, - name, - latest_released_version, - latest_release_date, - description, - last_sync_at, - last_sync_error, - webhook_created_at, - webhook_created_error, - last_scan_date, - last_scan_status, - last_scan_result, - keep_last_releases - FROM organization_package - WHERE organization_id = ? - AND name IN (?) - GROUP BY id', - [ - $organizationId, - $linkNames, - ], - [ - \PDO::PARAM_STR, - Connection::PARAM_STR_ARRAY, - ] - ); - - $items = []; - - foreach ($data as $row) { - $package = $this->hydratePackageDetails($row); - - $items[$package->name()] = $package; - } - - return $items; - } - /** * @return Version[] */ diff --git a/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php b/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php index 85466782..25650f02 100644 --- a/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php +++ b/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php @@ -19,6 +19,7 @@ use Composer\IO\BufferIO; use Composer\IO\IOInterface; use Composer\Package\CompletePackage; +use Composer\Package\Link; use Composer\Package\PackageInterface; use Composer\Repository\RepositoryFactory; use Composer\Repository\RepositoryInterface; @@ -150,27 +151,33 @@ public function synchronize(Package $package): void if ($latest->getVersion() === $version['version']) { $this->readmeExtractor->extractReadme($package, $dist); - // @todo Loop around all the types without copy/pasting so much - // Set the version links - foreach ($latest->getRequires() as $require) { - $package->addLink( - new Package\Link( - Uuid::uuid4()->toString(), - 'require', - $require->getTarget(), - $require->getPrettyConstraint(), - ) - ); + $types = ['requires', 'devRequires', 'provides', 'replaces', 'conflicts']; + + foreach ($types as $type) { + /** @var Link[] $links */ + $links = $latest->{"get{$type}"}(); + + foreach ($links as $link) { + $package->addLink( + new Package\Link( + Uuid::uuid4()->toString(), + $type, + $link->getTarget(), + $link->getPrettyConstraint(), + ) + ); + } } - foreach ($latest->getDevRequires() as $require) { + // suggests are different + foreach ($latest->getSuggests() as $linkName => $linkDescription) { $package->addLink( new Package\Link( Uuid::uuid4()->toString(), - 'devRequire', - $require->getTarget(), - $require->getPrettyConstraint(), + 'suggests', + $linkName, + $linkDescription, ) ); } diff --git a/templates/organization/package/details.html.twig b/templates/organization/package/details.html.twig index bdbd4cc2..40023216 100644 --- a/templates/organization/package/details.html.twig +++ b/templates/organization/package/details.html.twig @@ -149,28 +149,30 @@ {% if packageLinks|length > 0 %}
    -

    Dependencies

    - - {% for type,links in packageLinks %} - {{ type }} - {% for link in links %} -
  • - {% if link.targetPackageId %} - - {{ link.target }}: {{ link.constraint }} - - {% else %} - {{ link.target }}: {{ link.constraint }} - {% endif %} -
  • +
    + {% for type,links in packageLinks %} +
    +
    {{ type }}
    + {% for link in links %} +
  • + {% if link.targetPackageId %} + + {{ link.target }}: {{ link.constraint }} + + {% else %} + {{ link.target }}: {{ link.constraint }} + {% endif %} +
  • + {% endfor %} +
    {% endfor %} - {% endfor %} +
    {% endif %} {% if dependants|length > 0 %}
    -

    Dependants

    +

    Dependants (todo: remove)

    {% for link in dependants %}
  • From 56680bf08fe950f9ad2157e70ebc242c3084fb0d Mon Sep 17 00:00:00 2001 From: Joshua Gigg Date: Sun, 21 Mar 2021 18:15:00 +0000 Subject: [PATCH 03/10] Allow searching for dependants --- src/Controller/OrganizationController.php | 4 +- src/Entity/Organization/Package.php | 8 +- src/Migrations/Version20210309201702.php | 6 +- src/Query/User/PackageQuery.php | 5 +- .../User/PackageQuery/DbalPackageQuery.php | 100 ++++++++---------- src/Query/User/PackageQuery/Filter.php | 15 +++ .../ComposerPackageSynchronizer.php | 4 + templates/component/packageActions.html.twig | 5 + .../organization/package/details.html.twig | 20 +--- 9 files changed, 84 insertions(+), 83 deletions(-) diff --git a/src/Controller/OrganizationController.php b/src/Controller/OrganizationController.php index c0206037..12d1fc3a 100644 --- a/src/Controller/OrganizationController.php +++ b/src/Controller/OrganizationController.php @@ -111,7 +111,7 @@ public function packageDetails(Organization $organization, PackageDetails $packa $filter = Filter::fromRequest($request); $packageLinks = $this->packageQuery->getLinks($package->id(), $organization->id()); - $dependants = $this->packageQuery->getDependantLinks($package->name(), $organization->id()); + $dependantCount = $this->packageQuery->getDependantCount($package->name(), $organization->id()); $groupedPackageLinks = []; @@ -131,7 +131,7 @@ public function packageDetails(Organization $organization, PackageDetails $packa 'versions' => $this->packageQuery->getVersions($package->id(), $filter), 'installs' => $this->packageQuery->getInstalls($package->id(), 0), 'packageLinks' => $groupedPackageLinks, - 'dependants' => $dependants, + 'dependantCount' => $dependantCount, ]); } diff --git a/src/Entity/Organization/Package.php b/src/Entity/Organization/Package.php index 46f1b8f2..e8256c20 100644 --- a/src/Entity/Organization/Package.php +++ b/src/Entity/Organization/Package.php @@ -170,9 +170,10 @@ public function repositoryUrl(): string } /** + * @param string[] $encounteredVersions * @param string[] $encounteredVersions */ - public function syncSuccess(string $name, string $description, string $latestReleasedVersion, array $encounteredVersions, \DateTimeImmutable $latestReleaseDate): void + public function syncSuccess(string $name, string $description, string $latestReleasedVersion, array $encounteredVersions, array $encounteredLinks, \DateTimeImmutable $latestReleaseDate): void { $this->setName($name); $this->description = $description; @@ -183,6 +184,11 @@ public function syncSuccess(string $name, string $description, string $latestRel $this->versions->removeElement($version); } } + foreach ($this->links as $link) { + if (!in_array($link->type().'-'.$link->target(), $encounteredLinks, true)) { + $this->links->removeElement($link); + } + } $this->lastSyncAt = new \DateTimeImmutable(); $this->lastSyncError = null; } diff --git a/src/Migrations/Version20210309201702.php b/src/Migrations/Version20210309201702.php index 6d45a947..39805cb4 100644 --- a/src/Migrations/Version20210309201702.php +++ b/src/Migrations/Version20210309201702.php @@ -12,12 +12,12 @@ */ final class Version20210309201702 extends AbstractMigration { - public function getDescription() : string + public function getDescription(): string { return 'Add package links'; } - public function up(Schema $schema) : void + public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs @@ -30,7 +30,7 @@ public function up(Schema $schema) : void $this->addSql('ALTER TABLE organization_package_link ADD CONSTRAINT FK_CAKE4LIFE FOREIGN KEY (package_id) REFERENCES organization_package (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); } - public function down(Schema $schema) : void + public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs diff --git a/src/Query/User/PackageQuery.php b/src/Query/User/PackageQuery.php index d310ddd1..6465acd5 100644 --- a/src/Query/User/PackageQuery.php +++ b/src/Query/User/PackageQuery.php @@ -52,10 +52,7 @@ public function getVersions(string $packageId, Filter $filter): array; */ public function getLinks(string $packageId, string $organizationId): array; - /** - * @return Link[] - */ - public function getDependantLinks(string $packageName, string $organizationId): array; + public function getDependantCount(string $packageName, string $organizationId): int; public function getInstalls(string $packageId, int $lastDays = 30, ?string $version = null): Installs; diff --git a/src/Query/User/PackageQuery/DbalPackageQuery.php b/src/Query/User/PackageQuery/DbalPackageQuery.php index f81b43f9..2fb44821 100644 --- a/src/Query/User/PackageQuery/DbalPackageQuery.php +++ b/src/Query/User/PackageQuery/DbalPackageQuery.php @@ -32,24 +32,28 @@ public function __construct(Connection $connection) */ public function findAll(string $organizationId, Filter $filter): array { - $filterSQL = ''; + $filterSQL = $joinSQL = ''; $params = [ ':organization_id' => $organizationId, ':limit' => $filter->getLimit(), ':offset' => $filter->getOffset(), ]; - if ($filter->hasSearchTerm()) { - $filterSQL = ' AND (name ILIKE :term OR description ILIKE :term) '; + if ($filter->hasLinkSearch()) { + $filterSQL = ' AND l.target = :link'; + $params[':link'] = $filter->getLinkSearch(); + $joinSQL = 'JOIN organization_package_link l ON (p.id = l.package_id AND p.organization_id = :organization_id) '; + } elseif ($filter->hasSearchTerm()) { + $filterSQL = ' AND (p.name ILIKE :term OR p.description ILIKE :term)'; $params[':term'] = '%'.$filter->getSearchTerm().'%'; } $sortSQL = 'name ASC'; $sortColumnMappings = [ - 'name' => 'name', - 'version' => 'latest_released_version', - 'date' => 'latest_release_date', + 'name' => 'p.name', + 'version' => 'p.latest_released_version', + 'date' => 'p.latest_release_date', ]; if ($filter->hasSort() && isset($sortColumnMappings[$filter->getSortColumn()])) { @@ -62,26 +66,26 @@ function (array $data): Package { }, $this->connection->fetchAllAssociative( 'SELECT - id, - organization_id, - type, - repository_url, - name, - latest_released_version, - latest_release_date, - description, - last_sync_at, - last_sync_error, - webhook_created_at, - webhook_created_error, - last_scan_date, - last_scan_status, - last_scan_result, - keep_last_releases - FROM organization_package - WHERE organization_id = :organization_id + p.id, + p.organization_id, + p.type, + p.repository_url, + p.name, + p.latest_released_version, + p.latest_release_date, + p.description, + p.last_sync_at, + p.last_sync_error, + p.webhook_created_at, + p.webhook_created_error, + p.last_scan_date, + p.last_scan_status, + p.last_scan_result, + p.keep_last_releases + FROM organization_package p '.$joinSQL + .'WHERE p.organization_id = :organization_id '.$filterSQL.' - GROUP BY id + GROUP BY p.id ORDER BY '.$sortSQL.' LIMIT :limit OFFSET :offset', $params @@ -107,21 +111,25 @@ public function getAllNames(string $organizationId): array public function count(string $organizationId, Filter $filter): int { - $filterSQL = ''; + $filterSQL = $joinSQL = ''; $params = [ ':organization_id' => $organizationId, ]; - if ($filter->hasSearchTerm()) { - $filterSQL = ' AND (name ILIKE :term OR description ILIKE :term)'; + if ($filter->hasLinkSearch()) { + $filterSQL = ' AND l.target = :link'; + $params[':link'] = $filter->getLinkSearch(); + $joinSQL = 'JOIN organization_package_link l ON (p.id = l.package_id AND p.organization_id = :organization_id) '; + } elseif ($filter->hasSearchTerm()) { + $filterSQL = ' AND (p.name ILIKE :term OR p.description ILIKE :term)'; $params[':term'] = '%'.$filter->getSearchTerm().'%'; } return (int) $this ->connection ->fetchOne( - 'SELECT COUNT(id) FROM "organization_package" - WHERE organization_id = :organization_id'.$filterSQL, + 'SELECT COUNT(DISTINCT p.id) FROM organization_package p '.$joinSQL + .'WHERE p.organization_id = :organization_id'.$filterSQL, $params ); } @@ -207,35 +215,17 @@ public function versionCount(string $packageId): int ); } - /** - * @return Link[] - */ - public function getDependantLinks(string $packageName, string $organizationId): array + public function getDependantCount(string $packageName, string $organizationId): int { - return array_map(function (array $data): Link { - return (new Link( - $data['id'], - $data['type'], - $data['target'], - $data['constraint'], - $data['package_id'] - )) - ->setPackageName($data['name']); - }, $this->connection->fetchAllAssociative( + return (int) $this->connection->fetchOne( 'SELECT - l.id, - l.type, - l.target, - l.constraint, - p.id as package_id, - p.name - FROM organization_package_link l - JOIN organization_package p ON (p.id = l.package_id AND p.organization_id = l.organization_id) - WHERE l.target = :package_name - AND l.organization_id = :organization_id', [ + COUNT(DISTINCT package_id) + FROM organization_package_link + WHERE target = :package_name + AND organization_id = :organization_id', [ ':package_name' => $packageName, ':organization_id' => $organizationId, - ])); + ]); } /** diff --git a/src/Query/User/PackageQuery/Filter.php b/src/Query/User/PackageQuery/Filter.php index d1f1c170..26a20acd 100644 --- a/src/Query/User/PackageQuery/Filter.php +++ b/src/Query/User/PackageQuery/Filter.php @@ -27,6 +27,21 @@ public function hasSearchTerm(): bool return $this->searchTerm !== null; } + public function hasLinkSearch(): bool + { + // @todo Allow more search types? + return $this->searchTerm !== null && str_starts_with($this->searchTerm, 'depends:'); + } + + public function getLinkSearch(): ?string + { + if ($this->searchTerm === null) { + return null; + } + + return substr($this->searchTerm, strlen('depends:')); + } + public function getQueryStringParams(): array { $params = parent::getQueryStringParams(); diff --git a/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php b/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php index 25650f02..1f359dc2 100644 --- a/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php +++ b/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php @@ -115,6 +115,7 @@ public function synchronize(Package $package): void usort($versions, fn ($item1, $item2) => $item2['releaseDate'] <=> $item1['releaseDate']); $encounteredVersions = []; + $encounteredLinks = []; foreach ($versions as $version) { $dist = new Dist( $version['organizationAlias'], @@ -167,6 +168,7 @@ public function synchronize(Package $package): void $link->getPrettyConstraint(), ) ); + $encounteredLinks[] = $type.'-'.$link->getTarget(); } } @@ -180,6 +182,7 @@ public function synchronize(Package $package): void $linkDescription, ) ); + $encounteredLinks[] = 'suggests-'.$linkName; } } @@ -202,6 +205,7 @@ public function synchronize(Package $package): void $latest instanceof CompletePackage ? ($latest->getDescription() ?? 'n/a') : 'n/a', $latest->getStability() === Version::STABILITY_STABLE ? $latest->getPrettyVersion() : 'no stable release', $encounteredVersions, + $encounteredLinks, \DateTimeImmutable::createFromMutable($latest->getReleaseDate() ?? new \DateTime()), ); diff --git a/templates/component/packageActions.html.twig b/templates/component/packageActions.html.twig index 973526cc..2d719de4 100644 --- a/templates/component/packageActions.html.twig +++ b/templates/component/packageActions.html.twig @@ -13,6 +13,11 @@ {% include 'svg/bar-chart.svg' %} Statistics {% endif %} + {% if currentPath != 'organization_packages' and dependantCount > 0 %} + + {% include 'svg/package.svg' %} {{ dependantCount }} dependant{% if dependantCount > 1 %}s{% endif %} + + {% endif %} {% if currentPath != 'organization_package_webhook' %} {% include 'svg/link.svg' %} Webhook diff --git a/templates/organization/package/details.html.twig b/templates/organization/package/details.html.twig index 40023216..0024496a 100644 --- a/templates/organization/package/details.html.twig +++ b/templates/organization/package/details.html.twig @@ -152,13 +152,11 @@
    {% for type,links in packageLinks %}
    -
    {{ type }}
    +

    {{ type }}

    {% for link in links %}
  • {% if link.targetPackageId %} - - {{ link.target }}: {{ link.constraint }} - + {{ link.target }}: {{ link.constraint }} {% else %} {{ link.target }}: {{ link.constraint }} {% endif %} @@ -169,20 +167,6 @@ {% endif %} - {% if dependants|length > 0 %} -
    - -

    Dependants (todo: remove)

    - - {% for link in dependants %} -
  • - - {{ link.packageName }}: {{ link.constraint }} - -
  • - {% endfor %} - {% endif %} - {% if package.readme() %}
    From c852e2bb5e9bd4538d7631c10c438e835d47314b Mon Sep 17 00:00:00 2001 From: Joshua Gigg Date: Wed, 31 Mar 2021 17:15:32 +0100 Subject: [PATCH 04/10] Slightly nicer looking package list --- .../organization/package/details.html.twig | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/templates/organization/package/details.html.twig b/templates/organization/package/details.html.twig index 0024496a..d5cfc209 100644 --- a/templates/organization/package/details.html.twig +++ b/templates/organization/package/details.html.twig @@ -149,19 +149,34 @@ {% if packageLinks|length > 0 %}
    -
    - {% for type,links in packageLinks %} -
    -

    {{ type }}

    - {% for link in links %} -
  • - {% if link.targetPackageId %} - {{ link.target }}: {{ link.constraint }} - {% else %} - {{ link.target }}: {{ link.constraint }} - {% endif %} -
  • - {% endfor %} +
    + {% set types = { + 'requires': 'Requirements', + 'devRequires': 'Dev Requirements', + 'suggests': 'Suggestions', + 'provides': 'Provides', + 'conflicts': 'Conflicts', + 'replaces': 'Replaces', + } %} + + {% for type,label in types %} +
    +

    {{ label }}

    + {% if packageLinks[type] is defined and packageLinks[type]|length > 0 %} +
      + {% for link in packageLinks[type] %} +
    • + {% if link.targetPackageId %} + {{ link.target }}: {{ link.constraint }} + {% else %} + {{ link.target }}: {{ link.constraint }} + {% endif %} +
    • + {% endfor %} +
    + {% else %} + None + {% endif %}
    {% endfor %}
    From 851cdd1f2587733b40339c2bac2bc6f53354bd0e Mon Sep 17 00:00:00 2001 From: Joshua Gigg Date: Wed, 31 Mar 2021 21:51:47 +0100 Subject: [PATCH 05/10] Move dependant link to overview & update/fix tests --- phpstan.neon | 5 ++ src/Controller/OrganizationController.php | 6 ++- src/Entity/Organization/Package.php | 2 +- src/Entity/Organization/Package/Link.php | 7 +-- .../User/PackageQuery/DbalPackageQuery.php | 3 +- .../ComposerPackageSynchronizer.php | 29 ++++++------ templates/component/packageActions.html.twig | 5 -- .../organization/package/details.html.twig | 10 +++- tests/Doubles/FakePackageSynchronizer.php | 18 +++++++- .../Controller/OrganizationControllerTest.php | 43 +++++++++++++++++- tests/Integration/FixturesManager.php | 6 ++- .../SynchronizePackageHandlerTest.php | 13 +++++- tests/MotherObject/PackageMother.php | 4 +- .../artifacts/buddy-works-alpha-1.1.0.zip | Bin 309 -> 300 bytes .../artifacts/buddy-works-alpha-1.2.0.zip | Bin 309 -> 289 bytes tests/Unit/Entity/PackageTest.php | 4 +- .../ComposerPackageSynchronizerTest.php | 7 +++ 17 files changed, 128 insertions(+), 34 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 1b604913..04fcb4bb 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -8,6 +8,11 @@ parameters: count: 1 path: src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php + - + message: "#^Variable method call on Composer\\\\Package\\\\PackageInterface\\.$#" + count: 1 + path: src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php + - message: "#^Variable property access on DateInterval\\.$#" count: 1 diff --git a/src/Controller/OrganizationController.php b/src/Controller/OrganizationController.php index 12d1fc3a..13dac790 100644 --- a/src/Controller/OrganizationController.php +++ b/src/Controller/OrganizationController.php @@ -111,7 +111,11 @@ public function packageDetails(Organization $organization, PackageDetails $packa $filter = Filter::fromRequest($request); $packageLinks = $this->packageQuery->getLinks($package->id(), $organization->id()); - $dependantCount = $this->packageQuery->getDependantCount($package->name(), $organization->id()); + + /** @var string $packageName */ + $packageName = $package->name(); + + $dependantCount = $this->packageQuery->getDependantCount($packageName, $organization->id()); $groupedPackageLinks = []; diff --git a/src/Entity/Organization/Package.php b/src/Entity/Organization/Package.php index e8256c20..f3280f9f 100644 --- a/src/Entity/Organization/Package.php +++ b/src/Entity/Organization/Package.php @@ -171,7 +171,7 @@ public function repositoryUrl(): string /** * @param string[] $encounteredVersions - * @param string[] $encounteredVersions + * @param string[] $encounteredLinks */ public function syncSuccess(string $name, string $description, string $latestReleasedVersion, array $encounteredVersions, array $encounteredLinks, \DateTimeImmutable $latestReleaseDate): void { diff --git a/src/Entity/Organization/Package/Link.php b/src/Entity/Organization/Package/Link.php index 14a78da6..665fae26 100644 --- a/src/Entity/Organization/Package/Link.php +++ b/src/Entity/Organization/Package/Link.php @@ -7,6 +7,7 @@ use Buddy\Repman\Entity\Organization; use Buddy\Repman\Entity\Organization\Package; use Doctrine\ORM\Mapping as ORM; +use Ramsey\Uuid\UuidInterface; /** * @ORM\Entity @@ -25,7 +26,7 @@ class Link * @ORM\Id() * @ORM\Column(type="uuid") */ - private string $id; + private UuidInterface $id; /** * @ORM\ManyToOne(targetEntity="Buddy\Repman\Entity\Organization") @@ -60,7 +61,7 @@ class Link private ?string $targetPackageId; public function __construct( - string $id, + UuidInterface $id, string $type, string $target, string $constraint, @@ -75,7 +76,7 @@ public function __construct( $this->targetPackageId = $targetPackageId; } - public function id(): string + public function id(): UuidInterface { return $this->id; } diff --git a/src/Query/User/PackageQuery/DbalPackageQuery.php b/src/Query/User/PackageQuery/DbalPackageQuery.php index 2fb44821..231a5608 100644 --- a/src/Query/User/PackageQuery/DbalPackageQuery.php +++ b/src/Query/User/PackageQuery/DbalPackageQuery.php @@ -17,6 +17,7 @@ use Buddy\Repman\Query\User\PackageQuery; use Doctrine\DBAL\Connection; use Munus\Control\Option; +use Ramsey\Uuid\Uuid; final class DbalPackageQuery implements PackageQuery { @@ -235,7 +236,7 @@ public function getLinks(string $packageId, string $organizationId): array { return array_map(function (array $data): Link { return new Link( - $data['id'], + Uuid::fromString($data['id']), $data['type'], $data['target'], $data['constraint'], diff --git a/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php b/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php index 1f359dc2..460c7826 100644 --- a/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php +++ b/src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php @@ -157,18 +157,21 @@ public function synchronize(Package $package): void foreach ($types as $type) { /** @var Link[] $links */ - $links = $latest->{"get{$type}"}(); - - foreach ($links as $link) { - $package->addLink( - new Package\Link( - Uuid::uuid4()->toString(), - $type, - $link->getTarget(), - $link->getPrettyConstraint(), - ) - ); - $encounteredLinks[] = $type.'-'.$link->getTarget(); + $functionName = 'get'.$type; + if (method_exists($latest, $functionName)) { + $links = $latest->{$functionName}(); + + foreach ($links as $link) { + $package->addLink( + new Package\Link( + Uuid::uuid4(), + $type, + $link->getTarget(), + $link->getPrettyConstraint(), + ) + ); + $encounteredLinks[] = $type.'-'.$link->getTarget(); + } } } @@ -176,7 +179,7 @@ public function synchronize(Package $package): void foreach ($latest->getSuggests() as $linkName => $linkDescription) { $package->addLink( new Package\Link( - Uuid::uuid4()->toString(), + Uuid::uuid4(), 'suggests', $linkName, $linkDescription, diff --git a/templates/component/packageActions.html.twig b/templates/component/packageActions.html.twig index 2d719de4..973526cc 100644 --- a/templates/component/packageActions.html.twig +++ b/templates/component/packageActions.html.twig @@ -13,11 +13,6 @@ {% include 'svg/bar-chart.svg' %} Statistics {% endif %} - {% if currentPath != 'organization_packages' and dependantCount > 0 %} - - {% include 'svg/package.svg' %} {{ dependantCount }} dependant{% if dependantCount > 1 %}s{% endif %} - - {% endif %} {% if currentPath != 'organization_package_webhook' %} {% include 'svg/link.svg' %} Webhook diff --git a/templates/organization/package/details.html.twig b/templates/organization/package/details.html.twig index d5cfc209..77d6fc99 100644 --- a/templates/organization/package/details.html.twig +++ b/templates/organization/package/details.html.twig @@ -28,7 +28,7 @@
    -
    + +

    Security

    diff --git a/tests/Doubles/FakePackageSynchronizer.php b/tests/Doubles/FakePackageSynchronizer.php index 62ff18fd..02718f65 100644 --- a/tests/Doubles/FakePackageSynchronizer.php +++ b/tests/Doubles/FakePackageSynchronizer.php @@ -5,6 +5,7 @@ namespace Buddy\Repman\Tests\Doubles; use Buddy\Repman\Entity\Organization\Package; +use Buddy\Repman\Entity\Organization\Package\Link; use Buddy\Repman\Entity\Organization\Package\Version; use Buddy\Repman\Service\PackageSynchronizer; @@ -22,6 +23,11 @@ final class FakePackageSynchronizer implements PackageSynchronizer */ private array $versions = []; + /** + * @var Link[] + */ + private array $links = []; + public function __construct() { $this->latestReleaseDate = new \DateTimeImmutable(); @@ -29,8 +35,9 @@ public function __construct() /** * @param Version[] $versions + * @param Link[] $links */ - public function setData(string $name, string $description, string $latestReleasedVersion, \DateTimeImmutable $latestReleaseDate, array $versions = [], ?string $readme = null): void + public function setData(string $name, string $description, string $latestReleasedVersion, \DateTimeImmutable $latestReleaseDate, array $versions = [], array $links = [], ?string $readme = null): void { $this->name = $name; $this->description = $description; @@ -38,6 +45,7 @@ public function setData(string $name, string $description, string $latestRelease $this->latestReleaseDate = $latestReleaseDate; $this->error = null; $this->versions = $versions; + $this->links = $links; $this->readme = $readme; } @@ -64,11 +72,19 @@ public function synchronize(Package $package): void return $version->version(); }, $this->versions); + $encounteredLinks = []; + + foreach ($this->links as $link) { + $package->addLink($link); + $encounteredLinks[] = $link->type().'-'.$link->target(); + } + $package->syncSuccess( $this->name, $this->description, $this->latestReleasedVersion, $encounteredVersions, + $encounteredLinks, $this->latestReleaseDate ); } diff --git a/tests/Functional/Controller/OrganizationControllerTest.php b/tests/Functional/Controller/OrganizationControllerTest.php index 137087de..e48abbb1 100644 --- a/tests/Functional/Controller/OrganizationControllerTest.php +++ b/tests/Functional/Controller/OrganizationControllerTest.php @@ -4,6 +4,7 @@ namespace Buddy\Repman\Tests\Functional\Controller; +use Buddy\Repman\Entity\Organization\Package\Link; use Buddy\Repman\Entity\Organization\Package\Metadata; use Buddy\Repman\Entity\Organization\Package\Version; use Buddy\Repman\Entity\User\OAuthToken; @@ -163,6 +164,29 @@ public function testPackageSearch(): void self::assertStringContainsString('search=buddy', $response); } + public function testDependantSearch(): void + { + $buddyId = $this->fixtures->createOrganization('buddy', $this->userId); + + $packageId = $this->fixtures->addPackage($buddyId, 'https://buddy.com'); + $this->fixtures->syncPackageWithData($packageId, 'buddy-works/testing', '1', '1.1.1', new \DateTimeImmutable()); + + $packageId2 = $this->fixtures->addPackage($buddyId, 'https://buddy.com'); + $links = [ + new Link(Uuid::uuid4(), 'requires', 'buddy-works/testing', '^1.5'), + ]; + $this->fixtures->syncPackageWithData($packageId2, 'buddy-works/example', '2', '1.1.1', new \DateTimeImmutable(), [], $links); + + // Search for 'testing' (which is in name) + $this->client->request('GET', $this->urlTo('organization_packages', ['organization' => 'buddy', 'search' => 'depends:buddy-works/testing'])); + + self::assertTrue($this->client->getResponse()->isOk()); + $response = (string) $this->client->getResponse()->getContent(); + self::assertStringContainsString('1 entries', $response); + self::assertStringNotContainsString($packageId, $response); + self::assertStringContainsString($packageId2, $response); + } + public function testPagination(): void { $buddyId = $this->fixtures->createOrganization('buddy', $this->userId); @@ -459,10 +483,14 @@ public function testPackageDetails(): void new Version(Uuid::uuid4(), '1.0.1', 'ref2', 1048576, new \DateTimeImmutable(), Version::STABILITY_STABLE), new Version(Uuid::uuid4(), '1.1.0', 'lastref', 1073741824, new \DateTimeImmutable(), Version::STABILITY_STABLE), ]; - $this->fixtures->syncPackageWithData($packageId, 'buddy-works/buddy', 'Test', '1.1.1', new \DateTimeImmutable(), $versions, 'This is a readme'); + $links = [ + new Link(Uuid::uuid4(), 'requires', 'buddy-works/target', '^1.5'), + new Link(Uuid::uuid4(), 'suggests', 'buddy-works/buddy', '^2.0'), // Suggest self to test dependant link + ]; + $this->fixtures->syncPackageWithData($packageId, 'buddy-works/buddy', 'Test', '1.1.1', new \DateTimeImmutable(), $versions, $links, 'This is a readme'); $this->fixtures->addScanResult($packageId, 'ok'); - $this->client->request('GET', $this->urlTo('organization_package_details', [ + $crawler = $this->client->request('GET', $this->urlTo('organization_package_details', [ 'organization' => 'buddy', 'package' => $packageId, ])); @@ -475,6 +503,17 @@ public function testPackageDetails(): void self::assertStringContainsString($version->version(), $this->lastResponseBody()); self::assertStringContainsString($version->reference(), $this->lastResponseBody()); } + + $crawlerText = $crawler->text(null, true); + + self::assertStringContainsString('Requirements', $this->lastResponseBody()); + foreach ($links as $link) { + self::assertStringContainsString("{$link->target()}: {$link->constraint()}", $crawlerText); + } + + self::assertStringContainsString('Dependant Packages 1', $crawlerText); + self::assertStringContainsString('depends:buddy-works/buddy', $this->lastResponseBody()); + self::assertStringContainsString('This is a readme', $this->lastResponseBody()); $this->client->request('GET', $this->urlTo('organization_package_details', [ diff --git a/tests/Integration/FixturesManager.php b/tests/Integration/FixturesManager.php index ebf5d1e6..d4e2fe08 100644 --- a/tests/Integration/FixturesManager.php +++ b/tests/Integration/FixturesManager.php @@ -5,6 +5,7 @@ namespace Buddy\Repman\Tests\Integration; use Buddy\Repman\Entity\Organization\Member; +use Buddy\Repman\Entity\Organization\Package\Link; use Buddy\Repman\Entity\Organization\Package\ScanResult; use Buddy\Repman\Entity\Organization\Package\Version; use Buddy\Repman\Message\Admin\ChangeConfig; @@ -200,10 +201,11 @@ public function syncPackageWithError(string $packageId, string $error): void /** * @param Version[] $versions + * @param Link[] $links */ - public function syncPackageWithData(string $packageId, string $name, string $description, string $latestReleasedVersion, \DateTimeImmutable $latestReleaseDate, array $versions = [], ?string $readme = null): void + public function syncPackageWithData(string $packageId, string $name, string $description, string $latestReleasedVersion, \DateTimeImmutable $latestReleaseDate, array $versions = [], array $links = [], ?string $readme = null): void { - $this->container->get(PackageSynchronizer::class)->setData($name, $description, $latestReleasedVersion, $latestReleaseDate, $versions, $readme); + $this->container->get(PackageSynchronizer::class)->setData($name, $description, $latestReleasedVersion, $latestReleaseDate, $versions, $links, $readme); $this->dispatchMessage(new SynchronizePackage($packageId)); $this->container->get(EntityManagerInterface::class)->flush(); } diff --git a/tests/Integration/MessageHandler/Organization/SynchronizePackageHandlerTest.php b/tests/Integration/MessageHandler/Organization/SynchronizePackageHandlerTest.php index 2bd16872..7c0b46c2 100644 --- a/tests/Integration/MessageHandler/Organization/SynchronizePackageHandlerTest.php +++ b/tests/Integration/MessageHandler/Organization/SynchronizePackageHandlerTest.php @@ -4,11 +4,13 @@ namespace Buddy\Repman\Tests\Integration\MessageHandler\Organization; +use Buddy\Repman\Entity\Organization\Package\Link; use Buddy\Repman\Message\Organization\SynchronizePackage; use Buddy\Repman\Query\User\Model\Package; use Buddy\Repman\Query\User\PackageQuery\DbalPackageQuery; use Buddy\Repman\Service\PackageSynchronizer; use Buddy\Repman\Tests\Integration\IntegrationTestCase; +use Ramsey\Uuid\Uuid; final class SynchronizePackageHandlerTest extends IntegrationTestCase { @@ -16,11 +18,14 @@ public function testSuccess(): void { $organizationId = $this->fixtures->createOrganization('Buddy', $this->fixtures->createUser()); $packageId = $this->fixtures->addPackage($organizationId, 'https://github.com/buddy-works/repman', 'vcs'); + $link = new Link(Uuid::uuid4(), 'requires', 'buddy-works/target', '^1.5'); $this->container()->get(PackageSynchronizer::class)->setData( $name = 'buddy-works/repman', $description = 'Repman - PHP repository manager', $version = '2.0.0', - $date = new \DateTimeImmutable() + $date = new \DateTimeImmutable(), + [], + [$link], ); $this->dispatchMessage(new SynchronizePackage($packageId)); @@ -35,6 +40,12 @@ public function testSuccess(): void /** @var \DateTimeImmutable $releaseDate */ $releaseDate = $package->latestReleaseDate(); self::assertEquals($date->format('Y-m-d H:i:s'), $releaseDate->format('Y-m-d H:i:s')); + + /** @var Link[] $packageLinks */ + $packageLinks = $this->container()->get(DbalPackageQuery::class)->getLinks($packageId, $organizationId); + self::assertCount(1, $packageLinks); + self::assertEquals($link->target(), $packageLinks[0]->target()); + self::assertEquals($link->constraint(), $packageLinks[0]->constraint()); } public function testHandlePackageNotFoundWithoutError(): void diff --git a/tests/MotherObject/PackageMother.php b/tests/MotherObject/PackageMother.php index f53cdf4d..6d4547c4 100644 --- a/tests/MotherObject/PackageMother.php +++ b/tests/MotherObject/PackageMother.php @@ -55,8 +55,9 @@ public static function withOrganizationAndToken(string $type, string $url, strin /** * @param string[] $unencounteredVersions + * @param string[] $unencounteredLinks */ - public static function synchronized(string $name, string $latestVersion, string $url = '', array $unencounteredVersions = []): Package + public static function synchronized(string $name, string $latestVersion, string $url = '', array $unencounteredVersions = [], array $unencounteredLinks = []): Package { $package = new Package(Uuid::uuid4(), 'path', $url); $package->setOrganization(new Organization( @@ -70,6 +71,7 @@ public static function synchronized(string $name, string $latestVersion, string 'Package description', $latestVersion, $unencounteredVersions, + $unencounteredLinks, new \DateTimeImmutable() ); diff --git a/tests/Resources/artifacts/buddy-works-alpha-1.1.0.zip b/tests/Resources/artifacts/buddy-works-alpha-1.1.0.zip index 75736bd94245f5529cc5c2bca198b6822aa017ae..96f7a50807c9f992a97a1ef64468c635d3660fb4 100644 GIT binary patch literal 300 zcmWIWW@Zs#W?)rV+=Be#)FQpC;`}__zG$vP1_G}C zcXv(jH#A(?bRqYLe$UwiXWz+cD?^u5oA)0&aA0S8+wW|Sg~Ad`WEb|kuGEiSD}R_X zE{~z=%*I0(s^2c!7Q8L*nnI(f^F6*_g7YGdn*ZA1J;_^7t9MsjzN_|(2|jZ=b9Nhr zez4HLm9DSL47!*Ka42%pC3=AyCtn$^aGosT0-mGj8HH-}LK*n|uhXDZnm13*_ literal 309 zcmWIWW@Zs#-~hty+1>#RP|yUVc^MQKlJj#5@{3c8^slFA-B0PZgVz z^6YtI2_?66DeVZ$>5&W`r-0E}Yt8_at{Q%0(PC53*EA-?MS*xsA)JU$5QfciYYC{=em1 zE$dZzsfVE2q;7F##_<~(8#@MdJPXU64P6`*@TK%rp?$P1`$=VDL*i7_xT fNH8?iPd$*QcAXKO4)A7WgQ#I-hzBy(fH({QRhDDZ literal 309 zcmWIWW@Zs#-~hsh+1>#RP|yUVc^MQKlJj#5@{3c8^slFA-B0PZgVz z^6YtI2expectException(\RuntimeException::class); - $this->package->syncSuccess('../invalid/name', 'desc', '1.2.0.0', [], new \DateTimeImmutable()); + $this->package->syncSuccess('../invalid/name', 'desc', '1.2.0.0', [], [], new \DateTimeImmutable()); } public function testSyncSuccessRemovesUnencounteredVersions(): void @@ -32,7 +32,7 @@ public function testSyncSuccessRemovesUnencounteredVersions(): void $this->package->addOrUpdateVersion($version2 = new Version(Uuid::uuid4(), '1.0.1', 'anotherref', 5678, new \DateTimeImmutable(), Version::STABILITY_STABLE)); $this->package->addOrUpdateVersion($version3 = new Version(Uuid::uuid4(), '1.1.0', 'lastref', 6543, new \DateTimeImmutable(), Version::STABILITY_STABLE)); - $this->package->syncSuccess('some/package', 'desc', '1.1.0', ['1.0.0', '1.1.0'], new \DateTimeImmutable()); + $this->package->syncSuccess('some/package', 'desc', '1.1.0', ['1.0.0', '1.1.0'], [], new \DateTimeImmutable()); self::assertCount(2, $this->package->versions()); self::assertContains($version1, $this->package->versions()); diff --git a/tests/Unit/Service/PackageSynchronizer/ComposerPackageSynchronizerTest.php b/tests/Unit/Service/PackageSynchronizer/ComposerPackageSynchronizerTest.php index 4347d9d9..d3ea35e0 100644 --- a/tests/Unit/Service/PackageSynchronizer/ComposerPackageSynchronizerTest.php +++ b/tests/Unit/Service/PackageSynchronizer/ComposerPackageSynchronizerTest.php @@ -4,6 +4,7 @@ namespace Buddy\Repman\Tests\Unit\Service\PackageSynchronizer; +use Buddy\Repman\Entity\Organization\Package\Link; use Buddy\Repman\Entity\Organization\Package\Version; use Buddy\Repman\Repository\PackageRepository; use Buddy\Repman\Service\Dist\Storage; @@ -92,6 +93,12 @@ public function testSynchronizePackageFromArtifacts(): void }, $package->versions()->toArray()); sort($versionStrings, SORT_NATURAL); self::assertEquals(['1.0.0', '1.1.0', '1.1.1', '1.2.0'], $versionStrings); + + /** @var Link[] $links */ + $links = $package->links(); + self::assertCount(1, $links, 'The latest version has one link, an older one has two'); + self::assertEquals('php', $links[0]->target()); + self::assertEquals('^7.4.1', $links[0]->constraint()); } public function testSynchronizePackageThatAlreadyExists(): void From fbc6c2d15b935d21330a608717716fc29ad558f0 Mon Sep 17 00:00:00 2001 From: Joshua Gigg Date: Wed, 31 Mar 2021 21:56:52 +0100 Subject: [PATCH 06/10] Fix doctrine schema validation --- src/Entity/Organization/Package/Link.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Entity/Organization/Package/Link.php b/src/Entity/Organization/Package/Link.php index 665fae26..e2aac90c 100644 --- a/src/Entity/Organization/Package/Link.php +++ b/src/Entity/Organization/Package/Link.php @@ -13,10 +13,9 @@ * @ORM\Entity * @ORM\Table( * name="organization_package_link", - * uniqueConstraints={@ORM\UniqueConstraint(name="package_version", columns={"package_id", "version"})}, * indexes={ - * @ORM\Index(name="version_package_id_idx", columns={"package_id"}), - * @ORM\Index(name="version_date_idx", columns={"date"}) + * @ORM\Index(name="link_package_id_idx", columns={"package_id"}), + * @ORM\Index(name="link_target_idx", columns={"target"}), * } * ) */ From adf42479e7eb8765838f8a4f84e0d2b92917ac09 Mon Sep 17 00:00:00 2001 From: Joshua Gigg Date: Thu, 1 Apr 2021 08:32:30 +0100 Subject: [PATCH 07/10] Add missing foreign key --- src/Migrations/Version20210309201702.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Migrations/Version20210309201702.php b/src/Migrations/Version20210309201702.php index 39805cb4..a3d2118f 100644 --- a/src/Migrations/Version20210309201702.php +++ b/src/Migrations/Version20210309201702.php @@ -23,11 +23,13 @@ public function up(Schema $schema): void $this->addSql('CREATE TABLE organization_package_link (id UUID NOT NULL, organization_id UUID NOT NULL, package_id UUID NOT NULL, target VARCHAR(255) NOT NULL, "constraint" VARCHAR(255) NOT NULL, type VARCHAR (255) NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX link_package_id_idx ON organization_package_link (package_id)'); + $this->addSql('CREATE INDEX link_organization_id_idx ON organization_package_link (organization_id)'); $this->addSql('CREATE INDEX link_target_idx ON organization_package_link (target)'); $this->addSql('COMMENT ON COLUMN organization_package_link.id IS \'(DC2Type:uuid)\''); $this->addSql('COMMENT ON COLUMN organization_package_link.package_id IS \'(DC2Type:uuid)\''); $this->addSql('COMMENT ON COLUMN organization_package_link.organization_id IS \'(DC2Type:uuid)\''); $this->addSql('ALTER TABLE organization_package_link ADD CONSTRAINT FK_CAKE4LIFE FOREIGN KEY (package_id) REFERENCES organization_package (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE organization_package_link ADD CONSTRAINT FK_4A06082932C8A3DE FOREIGN KEY (organization_id) REFERENCES organization (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); } public function down(Schema $schema): void From 3670c50808d4a30e0f00c555e68e26f2c61051ae Mon Sep 17 00:00:00 2001 From: Joshua Gigg Date: Thu, 1 Apr 2021 13:32:20 +0100 Subject: [PATCH 08/10] Rename index to what doctrine suggests --- src/Migrations/Version20210309201702.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Migrations/Version20210309201702.php b/src/Migrations/Version20210309201702.php index a3d2118f..3a7086ac 100644 --- a/src/Migrations/Version20210309201702.php +++ b/src/Migrations/Version20210309201702.php @@ -23,7 +23,7 @@ public function up(Schema $schema): void $this->addSql('CREATE TABLE organization_package_link (id UUID NOT NULL, organization_id UUID NOT NULL, package_id UUID NOT NULL, target VARCHAR(255) NOT NULL, "constraint" VARCHAR(255) NOT NULL, type VARCHAR (255) NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX link_package_id_idx ON organization_package_link (package_id)'); - $this->addSql('CREATE INDEX link_organization_id_idx ON organization_package_link (organization_id)'); + $this->addSql('CREATE INDEX IDX_4A06082932C8A3DE ON organization_package_link (organization_id)'); $this->addSql('CREATE INDEX link_target_idx ON organization_package_link (target)'); $this->addSql('COMMENT ON COLUMN organization_package_link.id IS \'(DC2Type:uuid)\''); $this->addSql('COMMENT ON COLUMN organization_package_link.package_id IS \'(DC2Type:uuid)\''); From cdf7c35aae9058721a9a1f13b5275b3bdad8f5c5 Mon Sep 17 00:00:00 2001 From: Joshua Gigg Date: Thu, 1 Apr 2021 14:03:14 +0100 Subject: [PATCH 09/10] Test each of the link types --- .../artifacts/buddy-works-alpha-1.2.0.zip | Bin 289 -> 362 bytes .../ComposerPackageSynchronizerTest.php | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/Resources/artifacts/buddy-works-alpha-1.2.0.zip b/tests/Resources/artifacts/buddy-works-alpha-1.2.0.zip index 317a955665292595e5ece790f3f83a5106bb7ccb..b200b7922e9b4e9203c1f568b76e597d2623686e 100644 GIT binary patch literal 362 zcmWIWW@Zs#W?K+7T>hc#466;U6Od122$H&d)8#FHSAe%PP*#TiWNyb;v-# z^}DU>iCf2(a5P;nchiihRe~j7H^2(EGqoPw7RBvXWO^4|H_Ur*dF|`e_GMki&9s8 zwC9)9oQ+;Mjz{nuM&``0+`n%e7MszyBo0ScshLIs2$oLB4 GFaQ7q>WPd1 literal 289 zcmWIWW@Zs#W?E}Yt8_at{Q%0(PC53*EA-?MS*xsA)JU$5QfciYYC{=em1 zE$dZzsfVE2q;7F##_<~(8#@MdJPXU64P6`*@TK%rp?$P1`$=VDL*i7_xT fNH8?iPd$*QcAXKO4)A7WgQ#I-hzBy(fH({QRhDDZ diff --git a/tests/Unit/Service/PackageSynchronizer/ComposerPackageSynchronizerTest.php b/tests/Unit/Service/PackageSynchronizer/ComposerPackageSynchronizerTest.php index d3ea35e0..5b3a10cd 100644 --- a/tests/Unit/Service/PackageSynchronizer/ComposerPackageSynchronizerTest.php +++ b/tests/Unit/Service/PackageSynchronizer/ComposerPackageSynchronizerTest.php @@ -95,10 +95,20 @@ public function testSynchronizePackageFromArtifacts(): void self::assertEquals(['1.0.0', '1.1.0', '1.1.1', '1.2.0'], $versionStrings); /** @var Link[] $links */ - $links = $package->links(); - self::assertCount(1, $links, 'The latest version has one link, an older one has two'); - self::assertEquals('php', $links[0]->target()); - self::assertEquals('^7.4.1', $links[0]->constraint()); + $links = $package->links()->toArray(); + self::assertCount(6, $links); + + $linkStrings = array_map( + fn (Link $link): string => $link->type().'-'.$link->target().'-'.$link->constraint(), + $links + ); + + self::assertContains('requires-php-^7.4.1', $linkStrings); + self::assertContains('devRequires-buddy-works/dev-^1.0', $linkStrings); + self::assertContains('provides-buddy-works/provide-^1.0', $linkStrings); + self::assertContains('replaces-buddy-works/replace-^1.0', $linkStrings); + self::assertContains('conflicts-buddy-works/conflict-^1.0', $linkStrings); + self::assertContains('suggests-buddy-works/suggests-You really should', $linkStrings); } public function testSynchronizePackageThatAlreadyExists(): void From 8fa3e95496bc405e905fb12bac597a339ca11411 Mon Sep 17 00:00:00 2001 From: Joshua Gigg Date: Thu, 1 Apr 2021 15:02:17 +0100 Subject: [PATCH 10/10] Improve test coverage --- src/Entity/Organization/Package/Link.php | 45 ------------------------ tests/Unit/Entity/PackageTest.php | 22 ++++++++++++ 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/src/Entity/Organization/Package/Link.php b/src/Entity/Organization/Package/Link.php index e2aac90c..816fb743 100644 --- a/src/Entity/Organization/Package/Link.php +++ b/src/Entity/Organization/Package/Link.php @@ -55,8 +55,6 @@ class Link private string $constraint; private ?string $packageId; - private ?string $packageName; - private ?string $targetPackageId; public function __construct( @@ -75,69 +73,26 @@ public function __construct( $this->targetPackageId = $targetPackageId; } - public function id(): UuidInterface - { - return $this->id; - } - public function type(): string { return $this->type; } - public function setType(string $type): self - { - $this->type = $type; - - return $this; - } - public function target(): string { return $this->target; } - public function setTarget(string $target): self - { - $this->target = $target; - - return $this; - } - public function constraint(): string { return $this->constraint; } - public function setConstraint(string $constraint): self - { - $this->constraint = $constraint; - - return $this; - } - - public function packageId(): ?string - { - return $this->packageId; - } - public function targetPackageId(): ?string { return $this->targetPackageId; } - public function packageName(): ?string - { - return $this->packageName; - } - - public function setPackageName(?string $packageName): self - { - $this->packageName = $packageName; - - return $this; - } - public function setOrganization(Organization $organization): void { if (isset($this->organization)) { diff --git a/tests/Unit/Entity/PackageTest.php b/tests/Unit/Entity/PackageTest.php index 970f469e..808291a9 100644 --- a/tests/Unit/Entity/PackageTest.php +++ b/tests/Unit/Entity/PackageTest.php @@ -5,6 +5,7 @@ namespace Buddy\Repman\Tests\Unit\Entity; use Buddy\Repman\Entity\Organization\Package; +use Buddy\Repman\Entity\Organization\Package\Link; use Buddy\Repman\Entity\Organization\Package\Version; use Buddy\Repman\Tests\MotherObject\PackageMother; use PHPUnit\Framework\TestCase; @@ -40,6 +41,27 @@ public function testSyncSuccessRemovesUnencounteredVersions(): void self::assertContains($version3, $this->package->versions()); } + public function testSyncSuccessRemovesUnencounteredLinks(): void + { + $this->package->addLink($link1 = new Link(Uuid::uuid4(), 'replaces', 'buddy-works/testone', '^1.0')); + $this->package->addLink($link2 = new Link(Uuid::uuid4(), 'replaces', 'buddy-works/testtwo', '^1.0')); + $this->package->addLink($link3 = new Link(Uuid::uuid4(), 'replaces', 'buddy-works/testthree', '^1.0')); + + $this->package->syncSuccess( + 'some/package', + 'desc', + '1.1.0', + [], + ['replaces-buddy-works/testone', 'replaces-buddy-works/testthree'], + new \DateTimeImmutable() + ); + + self::assertCount(2, $this->package->links()); + self::assertContains($link1, $this->package->links()); + self::assertNotContains($link2, $this->package->links()); + self::assertContains($link3, $this->package->links()); + } + public function testOuathTokenNotFound(): void { $this->expectException(\RuntimeException::class);