From 697c1a787cf6229eaa5673ec5f9fc6090a9e807e Mon Sep 17 00:00:00 2001 From: Simon Krull Date: Sat, 1 Jun 2024 16:32:40 +0200 Subject: [PATCH 01/13] TASK: Use workspaceName instead of ContentStreamId in AssetUsageProjection --- .../Infrastructure/DbalSchemaFactory.php | 13 +++ .../Workspace/WorkspaceProjection.php | 4 +- .../Classes/Controller/UsageController.php | 2 +- .../Classes/AssetUsage/Dto/AssetUsage.php | 4 +- .../AssetUsage/Dto/AssetUsageFilter.php | 20 ++--- .../AssetUsage/Dto/AssetUsageNodeAddress.php | 4 +- .../AssetUsage/Dto/AssetUsageReference.php | 8 +- .../Projection/AssetUsageProjection.php | 45 +++++------ .../Projection/AssetUsageRepository.php | 79 ++++++++----------- .../Service/ImageVariantGarbageCollector.php | 2 +- 10 files changed, 85 insertions(+), 96 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php b/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php index 8f2d455f009..b297fc6bc77 100644 --- a/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php +++ b/Neos.ContentRepository.Core/Classes/Infrastructure/DbalSchemaFactory.php @@ -115,6 +115,19 @@ public static function columnForNodeTypeName(string $columnName): Column ->setCustomSchemaOption('collation', 'ascii_general_ci'); } + /** + * The WorkspaceName is an utf8mb4 string, we should be able to sort it properly, but we don't need unicode here. + * + * @see NodeTypeName + */ + public static function columnForWorkspaceName(string $columnName): Column + { + return (new Column($columnName, Type::getType(Types::STRING))) + ->setLength(255) + ->setNotnull(true) + ->setCustomSchemaOption('collation', 'utf8mb4_unicode_520_ci'); + } + /** * @param AbstractSchemaManager $schemaManager * @param Table[] $tables diff --git a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjection.php b/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjection.php index a069f587577..460b4823315 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjection.php +++ b/Neos.ContentRepository.Core/Classes/Projection/Workspace/WorkspaceProjection.php @@ -117,8 +117,8 @@ private function determineRequiredSqlStatements(): array } $workspaceTable = new Table($this->tableName, [ - (new Column('workspacename', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), - (new Column('baseworkspacename', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), + DbalSchemaFactory::columnForWorkspaceName('workspacename')->setNotNull(true), + DbalSchemaFactory::columnForWorkspaceName('baseworkspacename')->setNotNull(false), (new Column('workspacetitle', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), (new Column('workspacedescription', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), (new Column('workspaceowner', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), diff --git a/Neos.Media.Browser/Classes/Controller/UsageController.php b/Neos.Media.Browser/Classes/Controller/UsageController.php index b3f65a5b53c..89b826814c6 100644 --- a/Neos.Media.Browser/Classes/Controller/UsageController.php +++ b/Neos.Media.Browser/Classes/Controller/UsageController.php @@ -98,7 +98,7 @@ public function relatedNodesAction(AssetInterface $asset) $contentRepository = $this->contentRepositoryRegistry->get($usage->getContentRepositoryId()); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->getContentStreamId()); + $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($usage->getWorkspaceName()); // FIXME: AssetUsageReference->workspaceName ? $nodeAggregate = $contentRepository->getContentGraph($workspace->workspaceName)->findNodeAggregateById( diff --git a/Neos.Neos/Classes/AssetUsage/Dto/AssetUsage.php b/Neos.Neos/Classes/AssetUsage/Dto/AssetUsage.php index 82aaaece5c9..5acba22b8e9 100644 --- a/Neos.Neos/Classes/AssetUsage/Dto/AssetUsage.php +++ b/Neos.Neos/Classes/AssetUsage/Dto/AssetUsage.php @@ -6,7 +6,7 @@ use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Flow\Annotations as Flow; /** @@ -17,7 +17,7 @@ { public function __construct( public string $assetId, - public ContentStreamId $contentStreamId, + public WorkspaceName $workspaceName, public OriginDimensionSpacePoint $originDimensionSpacePoint, public NodeAggregateId $nodeAggregateId, public string $propertyName, diff --git a/Neos.Neos/Classes/AssetUsage/Dto/AssetUsageFilter.php b/Neos.Neos/Classes/AssetUsage/Dto/AssetUsageFilter.php index 1e65ab39f75..6b6c1944772 100644 --- a/Neos.Neos/Classes/AssetUsage/Dto/AssetUsageFilter.php +++ b/Neos.Neos/Classes/AssetUsage/Dto/AssetUsageFilter.php @@ -4,7 +4,7 @@ namespace Neos\Neos\AssetUsage\Dto; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Flow\Annotations as Flow; /** @@ -15,7 +15,7 @@ { private function __construct( public ?string $assetId, - public ?ContentStreamId $contentStreamId, + public ?WorkspaceName $workspaceName, public bool $groupByAsset, public bool $groupByNode, public bool $includeVariantsOfAsset, @@ -29,27 +29,27 @@ public static function create(): self public function withAsset(string $assetId): self { - return new self($assetId, $this->contentStreamId, $this->groupByAsset, $this->groupByNode, $this->includeVariantsOfAsset); + return new self($assetId, $this->workspaceName, $this->groupByAsset, $this->groupByNode, $this->includeVariantsOfAsset); } - public function withContentStream(ContentStreamId $contentStreamId): self + public function withWorkspaceName(WorkspaceName $workspaceName): self { - return new self($this->assetId, $contentStreamId, $this->groupByAsset, $this->groupByNode, $this->includeVariantsOfAsset); + return new self($this->assetId, $workspaceName, $this->groupByAsset, $this->groupByNode, $this->includeVariantsOfAsset); } public function includeVariantsOfAsset(): self { - return new self($this->assetId, $this->contentStreamId, $this->groupByAsset, $this->groupByNode, true); + return new self($this->assetId, $this->workspaceName, $this->groupByAsset, $this->groupByNode, true); } public function groupByAsset(): self { - return new self($this->assetId, $this->contentStreamId, true, $this->groupByNode, $this->includeVariantsOfAsset); + return new self($this->assetId, $this->workspaceName, true, $this->groupByNode, $this->includeVariantsOfAsset); } public function groupByNode(): self { - return new self($this->assetId, $this->contentStreamId, $this->groupByAsset, true, $this->includeVariantsOfAsset); + return new self($this->assetId, $this->workspaceName, $this->groupByAsset, true, $this->includeVariantsOfAsset); } public function hasAssetId(): bool @@ -57,8 +57,8 @@ public function hasAssetId(): bool return $this->assetId !== null; } - public function hasContentStreamId(): bool + public function hasWorkspaceName(): bool { - return $this->contentStreamId !== null; + return $this->workspaceName !== null; } } diff --git a/Neos.Neos/Classes/AssetUsage/Dto/AssetUsageNodeAddress.php b/Neos.Neos/Classes/AssetUsage/Dto/AssetUsageNodeAddress.php index a501ce52b38..c410ff65261 100644 --- a/Neos.Neos/Classes/AssetUsage/Dto/AssetUsageNodeAddress.php +++ b/Neos.Neos/Classes/AssetUsage/Dto/AssetUsageNodeAddress.php @@ -6,7 +6,7 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Flow\Annotations as Flow; /** @@ -21,7 +21,7 @@ final readonly class AssetUsageNodeAddress { public function __construct( - public ContentStreamId $contentStreamId, + public WorkspaceName $workspaceName, public DimensionSpacePoint $dimensionSpacePoint, public NodeAggregateId $nodeAggregateId, ) { diff --git a/Neos.Neos/Classes/AssetUsage/Dto/AssetUsageReference.php b/Neos.Neos/Classes/AssetUsage/Dto/AssetUsageReference.php index 8af0a6e3d0d..cbd88e97379 100644 --- a/Neos.Neos/Classes/AssetUsage/Dto/AssetUsageReference.php +++ b/Neos.Neos/Classes/AssetUsage/Dto/AssetUsageReference.php @@ -7,7 +7,7 @@ use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Flow\Annotations as Flow; use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Model\Dto\UsageReference; @@ -21,7 +21,7 @@ final class AssetUsageReference extends UsageReference public function __construct( AssetInterface $asset, private readonly ContentRepositoryId $contentRepositoryId, - private readonly ContentStreamId $contentStreamId, + private readonly WorkspaceName $workspaceName, private readonly OriginDimensionSpacePoint $originDimensionSpacePointHash, private readonly NodeAggregateId $nodeAggregateId, ) { @@ -33,9 +33,9 @@ public function getContentRepositoryId(): ContentRepositoryId return $this->contentRepositoryId; } - public function getContentStreamId(): ContentStreamId + public function getWorkspaceName(): WorkspaceName { - return $this->contentStreamId; + return $this->workspaceName; } public function getOriginDimensionSpacePoint(): OriginDimensionSpacePoint diff --git a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php index ec762433ff9..7c616147bd9 100644 --- a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php +++ b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php @@ -7,13 +7,12 @@ use Doctrine\DBAL\Connection; use Doctrine\ORM\Exception\ORMException; use Neos\ContentRepository\Core\EventStore\EventInterface; -use Neos\ContentRepository\Core\Feature\ContentStreamForking\Event\ContentStreamWasForked; -use Neos\ContentRepository\Core\Feature\ContentStreamRemoval\Event\ContentStreamWasRemoved; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; use Neos\ContentRepository\Core\Feature\NodeModification\Event\NodePropertiesWereSet; use Neos\ContentRepository\Core\Feature\NodeRemoval\Event\NodeAggregateWasRemoved; use Neos\ContentRepository\Core\Feature\NodeVariation\Event\NodePeerVariantWasCreated; +use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceWasRemoved; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasDiscarded; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPartiallyDiscarded; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPartiallyPublished; @@ -85,9 +84,9 @@ public function whenNodeAggregateWithNodeWasCreated(NodeAggregateWithNodeWasCrea ); } $nodeAddress = new AssetUsageNodeAddress( - $event->getContentStreamId(), + $event->workspaceName, $event->getOriginDimensionSpacePoint()->toDimensionSpacePoint(), - $event->getNodeAggregateId() + $event->nodeAggregateId ); $this->repository->addUsagesForNode($nodeAddress, $assetIdsByProperty); } @@ -108,9 +107,9 @@ public function whenNodePropertiesWereSet(NodePropertiesWereSet $event, EventEnv ); } $nodeAddress = new AssetUsageNodeAddress( - $event->getContentStreamId(), + $event->workspaceName, $event->getOriginDimensionSpacePoint()->toDimensionSpacePoint(), - $event->getNodeAggregateId() + $event->nodeAggregateId ); $this->repository->addUsagesForNode($nodeAddress, $assetIdsByProperty); } @@ -118,7 +117,7 @@ public function whenNodePropertiesWereSet(NodePropertiesWereSet $event, EventEnv public function whenNodeAggregateWasRemoved(NodeAggregateWasRemoved $event): void { $this->repository->removeNode( - $event->getNodeAggregateId(), + $event->nodeAggregateId, $event->affectedOccupiedDimensionSpacePoints->toDimensionSpacePointSet() ); } @@ -129,42 +128,38 @@ public function whenNodePeerVariantWasCreated(NodePeerVariantWasCreated $event): $this->repository->copyDimensions($event->sourceOrigin, $event->peerOrigin); } - public function whenContentStreamWasForked(ContentStreamWasForked $event): void - { - $this->repository->copyContentStream( - $event->sourceContentStreamId, - $event->newContentStreamId - ); - } - public function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void { - $this->repository->removeContentStream($event->previousContentStreamId); + $this->repository->removeWorkspaceName($event->workspaceName); } + // @TODO: Check if asset is part of partially discarded + // use NodeIdsToPublishOrDiscard? public function whenWorkspaceWasPartiallyDiscarded(WorkspaceWasPartiallyDiscarded $event): void { - $this->repository->removeContentStream($event->previousContentStreamId); + $this->repository->removeWorkspaceName($event->workspaceName); } + // @TODO: Check if asset is part of partially published + // use NodeIdsToPublishOrDiscard? public function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublished $event): void { - $this->repository->removeContentStream($event->previousSourceContentStreamId); + $this->repository->removeWorkspaceName($event->sourceWorkspaceName); } public function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void { - $this->repository->removeContentStream($event->previousSourceContentStreamId); + $this->repository->removeWorkspaceName($event->targetWorkspaceName); } public function whenWorkspaceWasRebased(WorkspaceWasRebased $event): void { - $this->repository->removeContentStream($event->previousContentStreamId); + $this->repository->removeWorkspaceName($event->workspaceName); } - public function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): void + public function whenWorkspaceWasRemoved(WorkspaceWasRemoved $event): void { - $this->repository->removeContentStream($event->contentStreamId); + $this->repository->removeWorkspaceName($event->workspaceName); } @@ -260,13 +255,12 @@ public function canHandle(EventInterface $event): bool NodePropertiesWereSet::class, NodeAggregateWasRemoved::class, NodePeerVariantWasCreated::class, - ContentStreamWasForked::class, WorkspaceWasDiscarded::class, WorkspaceWasPartiallyDiscarded::class, WorkspaceWasPartiallyPublished::class, WorkspaceWasPublished::class, WorkspaceWasRebased::class, - ContentStreamWasRemoved::class, + WorkspaceWasRemoved::class ]); } @@ -277,13 +271,12 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void NodePropertiesWereSet::class => $this->whenNodePropertiesWereSet($event, $eventEnvelope), NodeAggregateWasRemoved::class => $this->whenNodeAggregateWasRemoved($event), NodePeerVariantWasCreated::class => $this->whenNodePeerVariantWasCreated($event), - ContentStreamWasForked::class => $this->whenContentStreamWasForked($event), WorkspaceWasDiscarded::class => $this->whenWorkspaceWasDiscarded($event), WorkspaceWasPartiallyDiscarded::class => $this->whenWorkspaceWasPartiallyDiscarded($event), WorkspaceWasPartiallyPublished::class => $this->whenWorkspaceWasPartiallyPublished($event), WorkspaceWasPublished::class => $this->whenWorkspaceWasPublished($event), WorkspaceWasRebased::class => $this->whenWorkspaceWasRebased($event), - ContentStreamWasRemoved::class => $this->whenContentStreamWasRemoved($event), + WorkspaceWasRemoved::class => $this->whenWorkspaceWasRemoved($event), default => throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), }; } diff --git a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php index 3de07925cc1..441d18b24c2 100644 --- a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php +++ b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php @@ -20,7 +20,7 @@ use Neos\ContentRepository\Core\Infrastructure\DbalSchemaDiff; use Neos\ContentRepository\Core\Infrastructure\DbalSchemaFactory; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Neos\AssetUsage\Dto\AssetIdAndOriginalAssetId; use Neos\Neos\AssetUsage\Dto\AssetIdsByProperty; use Neos\Neos\AssetUsage\Dto\AssetUsage; @@ -67,7 +67,7 @@ private function databaseSchema(): Schema $table = new Table($this->tableNamePrefix, [ (new Column('assetid', Type::getType(Types::STRING)))->setLength(40)->setNotnull(true)->setDefault(''), (new Column('originalassetid', Type::getType(Types::STRING)))->setLength(40)->setNotnull(false)->setDefault(null), - DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotNull(true), + DbalSchemaFactory::columnForWorkspaceName('workspacename')->setNotNull(true), DbalSchemaFactory::columnForNodeAggregateId('nodeaggregateid')->setNotNull(true), DbalSchemaFactory::columnForDimensionSpacePoint('origindimensionspacepoint')->setNotNull(false), DbalSchemaFactory::columnForDimensionSpacePointHash('origindimensionspacepointhash')->setNotNull(true), @@ -75,10 +75,10 @@ private function databaseSchema(): Schema ]); $table - ->addUniqueIndex(['assetid', 'originalassetid', 'contentstreamid', 'nodeaggregateid', 'origindimensionspacepointhash', 'propertyname'], 'assetperproperty') + ->addUniqueIndex(['assetid', 'originalassetid', 'workspacename', 'nodeaggregateid', 'origindimensionspacepointhash', 'propertyname'], 'assetperproperty') ->addIndex(['assetid']) ->addIndex(['originalassetid']) - ->addIndex(['contentstreamid']) + ->addIndex(['workspacename']) ->addIndex(['nodeaggregateid']) ->addIndex(['origindimensionspacepointhash']); @@ -105,12 +105,12 @@ public function findUsages(AssetUsageFilter $filter): AssetUsages $queryBuilder->setParameter('assetId', $filter->assetId); } - if ($filter->hasContentStreamId()) { - $queryBuilder->andWhere('contentStreamId = :contentStreamId'); - $queryBuilder->setParameter('contentStreamId', $filter->contentStreamId?->value); + if ($filter->hasWorkspaceName()) { + $queryBuilder->andWhere('workspacename = :workspaceName'); + $queryBuilder->setParameter('workspaceName', $filter->workspaceName?->value); } if ($filter->groupByAsset) { - $queryBuilder->addGroupBy('assetId'); + $queryBuilder->addGroupBy('assetid'); } if ($filter->groupByNode) { $queryBuilder->addGroupBy('nodeaggregateid'); @@ -125,11 +125,11 @@ public function findUsages(AssetUsageFilter $filter): AssetUsages get_debug_type($result) ), 1646320966); } - /** @var array{assetid: string, contentstreamid: string, origindimensionspacepointhash: string, origindimensionspacepoint: string, nodeaggregateid: string, propertyname: string} $row */ + /** @var array{assetid: string, workspacename: string, origindimensionspacepointhash: string, origindimensionspacepoint: string, nodeaggregateid: string, propertyname: string} $row */ foreach ($result->iterateAssociative() as $row) { yield new AssetUsage( $row['assetid'], - ContentStreamId::fromString($row['contentstreamid']), + WorkspaceName::fromString($row['workspacename']), OriginDimensionSpacePoint::fromJsonString($row['origindimensionspacepoint']), NodeAggregateId::fromString($row['nodeaggregateid']), $row['propertyname'] @@ -149,11 +149,11 @@ public function addUsagesForNode(AssetUsageNodeAddress $nodeAddress, AssetIdsByP { // Delete all asset usage entries for newly set properties to ensure that removed or replaced assets are reflected $this->dbal->executeStatement('DELETE FROM ' . $this->tableNamePrefix - . ' WHERE contentStreamId = :contentStreamId' - . ' AND nodeAggregateId = :nodeAggregateId' - . ' AND originDimensionSpacePointHash = :originDimensionSpacePointHash' - . ' AND propertyName IN (:propertyNames)', [ - 'contentStreamId' => $nodeAddress->contentStreamId->value, + . ' WHERE workspacename = :workspaceName' + . ' AND nodeaggregateid = :nodeAggregateId' + . ' AND origindimensionspacepointhash = :originDimensionSpacePointHash' + . ' AND propertyname IN (:propertyNames)', [ + 'workspaceName' => $nodeAddress->workspaceName->value, 'nodeAggregateId' => $nodeAddress->nodeAggregateId->value, 'originDimensionSpacePointHash' => $nodeAddress->dimensionSpacePoint->hash, 'propertyNames' => $assetIdsByProperty->propertyNames(), @@ -166,13 +166,13 @@ public function addUsagesForNode(AssetUsageNodeAddress $nodeAddress, AssetIdsByP foreach ($assetIdAndOriginalAssetIds as $assetIdAndOriginalAssetId) { try { $this->dbal->insert($this->tableNamePrefix, [ - 'assetId' => $assetIdAndOriginalAssetId->assetId, - 'originalAssetId' => $assetIdAndOriginalAssetId->originalAssetId, - 'contentStreamId' => $nodeAddress->contentStreamId->value, - 'nodeAggregateId' => $nodeAddress->nodeAggregateId->value, - 'originDimensionSpacePoint' => $nodeAddress->dimensionSpacePoint->toJson(), - 'originDimensionSpacePointHash' => $nodeAddress->dimensionSpacePoint->hash, - 'propertyName' => $propertyName, + 'assetid' => $assetIdAndOriginalAssetId->assetId, + 'originalassetid' => $assetIdAndOriginalAssetId->originalAssetId, + 'workspacename' => $nodeAddress->workspaceName->value, + 'nodeaggregateid' => $nodeAddress->nodeAggregateId->value, + 'origindimensionspacepoint' => $nodeAddress->dimensionSpacePoint->toJson(), + 'origindimensionspacepointhash' => $nodeAddress->dimensionSpacePoint->hash, + 'propertyname' => $propertyName, ]); } catch (UniqueConstraintViolationException $e) { // A usage already exists for this node and property -> can be ignored @@ -181,26 +181,9 @@ public function addUsagesForNode(AssetUsageNodeAddress $nodeAddress, AssetIdsByP } } - public function removeContentStream(ContentStreamId $contentStreamId): void + public function removeWorkspaceName(WorkspaceName $workspaceName): void { - $this->dbal->delete($this->tableNamePrefix, ['contentStreamId' => $contentStreamId->value]); - } - - public function copyContentStream( - ContentStreamId $sourceContentStreamId, - ContentStreamId $targetContentStreamId, - ): void { - $this->dbal->executeStatement( - 'INSERT INTO ' . $this->tableNamePrefix . ' (assetid, originalassetid, contentstreamid, nodeaggregateid, origindimensionspacepoint, origindimensionspacepointhash, propertyname)' - . ' SELECT assetid, originalassetid, :targetContentStreamId AS contentstreamid,' - . ' nodeaggregateid, origindimensionspacepoint, origindimensionspacepointhash, propertyname' - . ' FROM ' . $this->tableNamePrefix - . ' WHERE contentStreamId = :sourceContentStreamId', - [ - 'sourceContentStreamId' => $sourceContentStreamId->value, - 'targetContentStreamId' => $targetContentStreamId->value, - ] - ); + $this->dbal->delete($this->tableNamePrefix, ['workspaceName' => $workspaceName->value]); } public function copyDimensions( @@ -209,8 +192,8 @@ public function copyDimensions( ): void { try { $this->dbal->executeStatement( - 'INSERT INTO ' . $this->tableNamePrefix . ' (assetid, originalassetid, contentstreamid, nodeaggregateid, origindimensionspacepoint, origindimensionspacepointhash, propertyname)' - . ' SELECT assetid, originalassetid, contentstreamid, nodeaggregateid,' + 'INSERT INTO ' . $this->tableNamePrefix . ' (assetid, originalassetid, workspacename, nodeaggregateid, origindimensionspacepoint, origindimensionspacepointhash, propertyname)' + . ' SELECT assetid, originalassetid, workspacename, nodeaggregateid,' . ' :targetOriginDimensionSpacePoint AS origindimensionspacepoint,' . ' :targetOriginDimensionSpacePointHash AS origindimensionspacepointhash, propertyname' . ' FROM ' . $this->tableNamePrefix @@ -229,11 +212,11 @@ public function copyDimensions( public function remove(AssetUsage $usage): void { $this->dbal->delete($this->tableNamePrefix, [ - 'assetId' => $usage->assetId, - 'contentStreamId' => $usage->contentStreamId->value, - 'nodeAggregateId' => $usage->nodeAggregateId->value, - 'originDimensionSpacePointHash' => $usage->originDimensionSpacePoint->hash, - 'propertyName' => $usage->propertyName, + 'assetid' => $usage->assetId, + 'workspacename' => $usage->workspaceName->value, + 'nodeaggregateid' => $usage->nodeAggregateId->value, + 'origindimensionspacepointhash' => $usage->originDimensionSpacePoint->hash, + 'propertyname' => $usage->propertyName, ]); } diff --git a/Neos.Neos/Classes/Service/ImageVariantGarbageCollector.php b/Neos.Neos/Classes/Service/ImageVariantGarbageCollector.php index 0f4e21b4838..e4e9d142f15 100644 --- a/Neos.Neos/Classes/Service/ImageVariantGarbageCollector.php +++ b/Neos.Neos/Classes/Service/ImageVariantGarbageCollector.php @@ -77,7 +77,7 @@ public function removeUnusedImageVariant(Node $node, $propertyName, $oldValue, $ // then we are safe to remove the asset here. if ( $usageItem instanceof AssetUsageReference - && $usageItem->getContentStreamId()->equals($node->subgraphIdentity->contentStreamId) + && $usageItem->getWorkspaceName()->equals($node->workspaceName) && $usageItem->getOriginDimensionSpacePoint()->equals($node->originDimensionSpacePoint) && $usageItem->getNodeAggregateId()->equals($node->aggregateId) ) { From 791c5eee5f59e4b736762198cf515d8b6ba756f5 Mon Sep 17 00:00:00 2001 From: Simon Krull Date: Mon, 3 Jun 2024 11:38:53 +0200 Subject: [PATCH 02/13] TASK: introduce and use removeNodeInWorkspace method --- .../Projection/AssetUsageProjection.php | 26 +++++++++++++------ .../Projection/AssetUsageRepository.php | 9 ++++--- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php index 7c616147bd9..6057e5657fe 100644 --- a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php +++ b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php @@ -6,6 +6,7 @@ use Doctrine\DBAL\Connection; use Doctrine\ORM\Exception\ORMException; +use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; @@ -116,9 +117,10 @@ public function whenNodePropertiesWereSet(NodePropertiesWereSet $event, EventEnv public function whenNodeAggregateWasRemoved(NodeAggregateWasRemoved $event): void { - $this->repository->removeNode( + $this->repository->removeNodeInWorkspace( $event->nodeAggregateId, - $event->affectedOccupiedDimensionSpacePoints->toDimensionSpacePointSet() + $event->affectedOccupiedDimensionSpacePoints->toDimensionSpacePointSet(), + $event->workspaceName ); } @@ -133,18 +135,26 @@ public function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void $this->repository->removeWorkspaceName($event->workspaceName); } - // @TODO: Check if asset is part of partially discarded - // use NodeIdsToPublishOrDiscard? public function whenWorkspaceWasPartiallyDiscarded(WorkspaceWasPartiallyDiscarded $event): void { - $this->repository->removeWorkspaceName($event->workspaceName); + foreach ($event->discardedNodes as $discardedNode) { + $this->repository->removeNodeInWorkspace( + $discardedNode->nodeAggregateId, + DimensionSpacePointSet::fromArray([$discardedNode->dimensionSpacePoint]), + $event->workspaceName + ); + } } - // @TODO: Check if asset is part of partially published - // use NodeIdsToPublishOrDiscard? public function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublished $event): void { - $this->repository->removeWorkspaceName($event->sourceWorkspaceName); + foreach ($event->publishedNodes as $publishedNode) { + $this->repository->removeNodeInWorkspace( + $publishedNode->nodeAggregateId, + DimensionSpacePointSet::fromArray([$publishedNode->dimensionSpacePoint]), + $event->sourceWorkspaceName + ); + } } public function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void diff --git a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php index 441d18b24c2..c9e069bb98e 100644 --- a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php +++ b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php @@ -227,17 +227,20 @@ public function removeAsset(string $assetId): void ]); } - public function removeNode( + public function removeNodeInWorkspace( NodeAggregateId $nodeAggregateId, DimensionSpacePointSet $dimensionSpacePoints, + WorkspaceName $workspaceName ): void { $this->dbal->executeStatement( 'DELETE FROM ' . $this->tableNamePrefix - . ' WHERE nodeAggregateId = :nodeAggregateId' - . ' AND originDimensionSpacePointHash IN (:dimensionSpacePointHashes)', + . ' WHERE nodeaggregateid = :nodeAggregateId' + . ' AND origindimensionspacepointhash IN (:dimensionSpacePointHashes)' + . ' AND workspacename = :workspaceName', [ 'nodeAggregateId' => $nodeAggregateId->value, 'dimensionSpacePointHashes' => $dimensionSpacePoints->getPointHashes(), + 'workspaceName' => $workspaceName->value, ], [ 'dimensionSpacePointHashes' => Connection::PARAM_STR_ARRAY, From 38a3694bcb22e466e4a0103569fd99cd4951dec8 Mon Sep 17 00:00:00 2001 From: Simon Krull Date: Mon, 3 Jun 2024 11:39:28 +0200 Subject: [PATCH 03/13] TASK: replace contentStreamId with workspaceName where contentStreamId still was used --- .../src/Processors/AssetExportProcessor.php | 2 +- .../Classes/AssetUsage/AssetUsageStrategy.php | 2 +- .../AssetUsage/Service/AssetUsageSyncService.php | 3 +-- .../Classes/Fusion/Cache/ContentCacheFlusher.php | 16 +--------------- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/Neos.ContentRepository.Export/src/Processors/AssetExportProcessor.php b/Neos.ContentRepository.Export/src/Processors/AssetExportProcessor.php index db7679a18b9..0dff3d05138 100644 --- a/Neos.ContentRepository.Export/src/Processors/AssetExportProcessor.php +++ b/Neos.ContentRepository.Export/src/Processors/AssetExportProcessor.php @@ -47,7 +47,7 @@ public function run(): ProcessorResult if ($liveWorkspace === null) { return ProcessorResult::error('Failed to find live workspace'); } - $assetFilter = AssetUsageFilter::create()->withContentStream($liveWorkspace->currentContentStreamId)->groupByAsset(); + $assetFilter = AssetUsageFilter::create()->withWorkspaceName($liveWorkspace->workspaceName)->groupByAsset(); $numberOfExportedAssets = 0; $numberOfExportedImageVariants = 0; diff --git a/Neos.Neos/Classes/AssetUsage/AssetUsageStrategy.php b/Neos.Neos/Classes/AssetUsage/AssetUsageStrategy.php index 00e4e2dde35..444d12a435f 100644 --- a/Neos.Neos/Classes/AssetUsage/AssetUsageStrategy.php +++ b/Neos.Neos/Classes/AssetUsage/AssetUsageStrategy.php @@ -51,7 +51,7 @@ public function getUsageReferences(AssetInterface $asset): array $convertedUsages[] = new AssetUsageReference( $asset, ContentRepositoryId::fromString($contentRepositoryId), - $usage->contentStreamId, + $usage->workspaceName, $usage->originDimensionSpacePoint, $usage->nodeAggregateId ); diff --git a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php index f73bf9865d5..6f92b7a8044 100644 --- a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php +++ b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php @@ -55,8 +55,7 @@ public function isAssetUsageStillValid(AssetUsage $usage): bool } $dimensionSpacePoint = $usage->originDimensionSpacePoint->toDimensionSpacePoint(); - // FIXME: AssetUsage->workspaceName ? - $workspace = $this->contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->contentStreamId); + $workspace = $this->contentRepository->getWorkspaceFinder()->findOneByName($usage->workspaceName); if (is_null($workspace)) { return false; } diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index daea26cfeec..7d8665655b5 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -284,27 +284,13 @@ public function registerAssetChange(AssetInterface $asset): void ->includeVariantsOfAsset(); - $workspaceNamesByContentStreamId = []; foreach ($this->globalAssetUsageService->findByFilter($filter) as $contentRepositoryId => $usages) { foreach ($usages as $usage) { - // TODO: Remove when WorkspaceName is part of the AssetUsageProjection - $workspaceName = $workspaceNamesByContentStreamId[$contentRepositoryId][$usage->contentStreamId->value] ?? null; - if ($workspaceName === null) { - $contentRepository = $this->contentRepositoryRegistry->get(ContentRepositoryId::fromString($contentRepositoryId)); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->contentStreamId); - if ($workspace === null) { - continue; - } - $workspaceName = $workspace->workspaceName; - $workspaceNamesByContentStreamId[$contentRepositoryId][$usage->contentStreamId->value] = $workspaceName; - } - // - $contentRepository = $this->contentRepositoryRegistry->get(ContentRepositoryId::fromString($contentRepositoryId)); $tagsToFlush = array_merge( $this->collectTagsForChangeOnNodeAggregate( $contentRepository, - $workspaceName, + $usage->workspaceName, $usage->nodeAggregateId ), $tagsToFlush From 9a20d800c57ac6d6c5259bd411a450d8dc4ea233 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Mon, 3 Jun 2024 17:11:57 +0200 Subject: [PATCH 04/13] TASk: Add test cases for AssetUsageProjection --- .../Projection/AssetUsageProjection.php | 7 +- ...ggregateWithNode_WithoutDimensions.feature | 49 +++++++++++++ ...PublishWorkspace_WithoutDimensions.feature | 72 +++++++++++++++++++ .../Features/Bootstrap/AssetUsageTrait.php | 65 +++++++++++++++++ .../Features/Bootstrap/FeatureContext.php | 1 + 5 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature create mode 100644 Neos.Neos/Tests/Behavior/Features/Bootstrap/AssetUsageTrait.php diff --git a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php index 6057e5657fe..b3ec5087f77 100644 --- a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php +++ b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php @@ -159,7 +159,7 @@ public function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublishe public function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void { - $this->repository->removeWorkspaceName($event->targetWorkspaceName); + $this->repository->removeWorkspaceName($event->sourceWorkspaceName); } public function whenWorkspaceWasRebased(WorkspaceWasRebased $event): void @@ -265,6 +265,8 @@ public function canHandle(EventInterface $event): bool NodePropertiesWereSet::class, NodeAggregateWasRemoved::class, NodePeerVariantWasCreated::class, + // NodeGeneralizationVariantWasCreated::class, + // NodeSpecializationVariantWasCreated::class, WorkspaceWasDiscarded::class, WorkspaceWasPartiallyDiscarded::class, WorkspaceWasPartiallyPublished::class, @@ -281,6 +283,9 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void NodePropertiesWereSet::class => $this->whenNodePropertiesWereSet($event, $eventEnvelope), NodeAggregateWasRemoved::class => $this->whenNodeAggregateWasRemoved($event), NodePeerVariantWasCreated::class => $this->whenNodePeerVariantWasCreated($event), + // TODO + // NodeGeneralizationVariantWasCreated::class => "", + // NodeSpecializationVariantWasCreated::class => "", WorkspaceWasDiscarded::class => $this->whenWorkspaceWasDiscarded($event), WorkspaceWasPartiallyDiscarded::class => $this->whenWorkspaceWasPartiallyDiscarded($event), WorkspaceWasPartiallyPublished::class => $this->whenWorkspaceWasPartiallyPublished($event), diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature new file mode 100644 index 00000000000..b523d214178 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature @@ -0,0 +1,49 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Create node aggregate with node + + Background: Create node aggregate with initial node + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + And I am in workspace "live" + And I am in dimension space point {} + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + Scenario: Nodes on live workspace have been created + Given the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {} | + | asset-2 | nody-mc-nodeface | assets | live | {} | + | asset-3 | sir-nodeward-nodington-iii | text | live | {} | \ No newline at end of file diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature new file mode 100644 index 00000000000..096f329bd1e --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature @@ -0,0 +1,72 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Publish nodes without dimensions + + Background: + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-test" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + + And I am in workspace "live" + And I am in dimension space point {} + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + Scenario: Publish nodes from user workspace to live + Given I am in workspace "user-test" + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-test" | + And I am in dimension space point {} + + Then the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-test | {} | + | asset-2 | nody-mc-nodeface | assets | user-test | {} | + | asset-3 | sir-nodeward-nodington-iii | text | user-test | {} | + + And the command PublishWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-test" | + | newContentStreamId | "new-user-cs-id" | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {} | + | asset-2 | nody-mc-nodeface | assets | live | {} | + | asset-3 | sir-nodeward-nodington-iii | text | live | {} | \ No newline at end of file diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/AssetUsageTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/AssetUsageTrait.php new file mode 100644 index 00000000000..e28b4aff42a --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/AssetUsageTrait.php @@ -0,0 +1,65 @@ + $className + * + * @return T + */ + abstract private function getObject(string $className): object; + + /** + * @Then I expect the AssetUsageProjection to have the following AssetUsages: + */ + public function iExpectTheAssetUsageProjectionToHaveTheFollowingAssetUsages(TableNode $table) + { + $assetUsageFinder = $this->currentContentRepository->projectionState(AssetUsageFinder::class); + $assetUsages = $assetUsageFinder->findByFilter(AssetUsageFilter::create()); + + $tableRows = $table->getHash(); + foreach ($assetUsages as $assetUsage) { + foreach ($tableRows as $tableRowIndex => $tableRow) { + if ($assetUsage->assetId !== $tableRow['assetId'] + || $assetUsage->propertyName !== $tableRow['propertyName'] + || !$assetUsage->workspaceName->equals(WorkspaceName::fromString($tableRow['workspaceName'])) + || !$assetUsage->nodeAggregateId->equals(NodeAggregateId::fromString($tableRow['nodeAggregateId'])) + || !$assetUsage->originDimensionSpacePoint->equals(DimensionSpacePoint::fromJsonString($tableRow['originDimensionSpacePoint'])) + ) { + continue; + } + unset($tableRows[$tableRowIndex]); + continue 2; + } + } + + Assert::assertEmpty($tableRows, "Not all given asset usages where found."); + Assert::assertSame($assetUsages->count(), count($table->getHash()), "More asset usages found as given."); + + } +} \ No newline at end of file diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php index ac1b37f4ba5..5e2c525774d 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php @@ -42,6 +42,7 @@ class FeatureContext implements BehatContext use FusionTrait; use ContentCacheTrait; + use AssetUsageTrait; use AssetTrait; protected Environment $environment; From 27f36275b538300019056092e9f8fb6b0367cb57 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Tue, 4 Jun 2024 17:00:44 +0200 Subject: [PATCH 05/13] TASK: Move array merging out of foreach loops. --- .../Fusion/Cache/ContentCacheFlusher.php | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index 7d8665655b5..bc06a3d6538 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -114,9 +114,6 @@ private function collectTagsForChangeOnNodeAggregate( ) { $parentNodeAggregates[] = $parentNodeAggregate; } - // we do not need these variables anymore here - unset($nodeAggregateId); - // NOTE: Normally, the content graph cannot contain cycles. However, during the // testcase "Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature" @@ -287,18 +284,15 @@ public function registerAssetChange(AssetInterface $asset): void foreach ($this->globalAssetUsageService->findByFilter($filter) as $contentRepositoryId => $usages) { foreach ($usages as $usage) { $contentRepository = $this->contentRepositoryRegistry->get(ContentRepositoryId::fromString($contentRepositoryId)); - $tagsToFlush = array_merge( - $this->collectTagsForChangeOnNodeAggregate( - $contentRepository, - $usage->workspaceName, - $usage->nodeAggregateId - ), - $tagsToFlush + $tagsToFlush[] = $this->collectTagsForChangeOnNodeAggregate( + $contentRepository, + $usage->workspaceName, + $usage->nodeAggregateId ); } } - - $this->tagsToFlushAfterPersistance = array_merge($tagsToFlush, $this->tagsToFlushAfterPersistance); + $tagsToFlush[] = $this->tagsToFlushAfterPersistance; + $this->tagsToFlushAfterPersistance = array_merge(...$tagsToFlush); } /** From 7ca17489b11b303d19124a082e9f6d34b3fe6639 Mon Sep 17 00:00:00 2001 From: Simon Krull Date: Wed, 5 Jun 2024 09:57:58 +0200 Subject: [PATCH 06/13] TASK: add behat test cases for NodeCreation and WorkspacePublication --- ...ggregateWithNode_WithoutDimensions.feature | 88 ++++++++-- ...etNodeProperties_WithoutDimensions.feature | 158 ++++++++++++++++++ ...PublishWorkspace_WithoutDimensions.feature | 96 +++++++++-- 3 files changed, 314 insertions(+), 28 deletions(-) create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeModification/01-SetNodeProperties_WithoutDimensions.feature diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature index b523d214178..69ecb137c20 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature @@ -1,8 +1,8 @@ @contentrepository @adapters=DoctrineDBAL @flowEntities -Feature: Create node aggregate with node +Feature: Publish nodes without dimensions - Background: Create node aggregate with initial node + Background: Given using no content dimensions And using the following node types: """yaml @@ -23,6 +23,7 @@ Feature: Create node aggregate with node | workspaceTitle | "Live" | | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | + And I am in workspace "live" And I am in dimension space point {} And I am user identified by "initiating-user-identifier" @@ -35,15 +36,82 @@ Feature: Create node aggregate with node And an asset exists with id "asset-2" And an asset exists with id "asset-3" - Scenario: Nodes on live workspace have been created - Given the following CreateNodeAggregateWithNode commands are executed: - | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | - | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | - | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | - | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + Scenario: Publish nodes from user workspace to live + Given the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + And I am in workspace "user-workspace" + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + And I am in dimension space point {} + + Then the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {} | + | asset-2 | nody-mc-nodeface | assets | live | {} | + | asset-3 | sir-nodeward-nodington-iii | text | live | {} | + + Scenario: Publish nodes from user workspace to a non live workspace + Given the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "review-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "review-workspace-cs-id" | + + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "review-workspace" | + + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "review-workspace" | + | newContentStreamId | "user-workspace-cs-id" | + + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + And I am in workspace "user-workspace" + + And I am in dimension space point {} + Then the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | review-workspace | {} | + | asset-2 | nody-mc-nodeface | assets | review-workspace | {} | + | asset-3 | sir-nodeward-nodington-iii | text | review-workspace | {} | - Then I expect the AssetUsageProjection to have the following AssetUsages: + And I expect the AssetUsageProjection to have the following AssetUsages: | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | | asset-1 | sir-david-nodenborough | asset | live | {} | | asset-2 | nody-mc-nodeface | assets | live | {} | - | asset-3 | sir-nodeward-nodington-iii | text | live | {} | \ No newline at end of file + | asset-3 | sir-nodeward-nodington-iii | text | live | {} | diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeModification/01-SetNodeProperties_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeModification/01-SetNodeProperties_WithoutDimensions.feature new file mode 100644 index 00000000000..684d2517cfb --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeModification/01-SetNodeProperties_WithoutDimensions.feature @@ -0,0 +1,158 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Create node aggregate with node + + Background: Create node aggregate with initial node + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + + Then the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + And I am in workspace "live" + And I am in dimension space point {} + + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + Then the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2", "Asset:asset-3"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3"} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And I am in workspace "user-workspace" + + Then the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + And I am in dimension space point {} + + Scenario: Set node properties without dimension and publish in user workspace + Given the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {} | + | propertyValues | {"asset": "Asset:asset-2"} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-david-nodenborough | asset-1 | asset | live | {} | + | sir-david-nodenborough | asset-2 | asset | user-workspace | {} | + | nody-mc-nodeface | asset-2 | assets | live | {} | + | nody-mc-nodeface | asset-3 | assets | live | {} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {} | + + Scenario: Remove an asset from an existing property + Given the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {} | + | propertyValues | {"asset": null} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-david-nodenborough | asset-1 | asset | live | {} | + | nody-mc-nodeface | asset-2 | assets | live | {} | + | nody-mc-nodeface | asset-3 | assets | live | {} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {} | + + Scenario: Remove an asset from an existing property from the live workspaces + Given I am in workspace "live" + And the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "live" | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {} | + | propertyValues | {} | + | propertiesToUnset | ["asset"] | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | nody-mc-nodeface | asset-2 | assets | live | {} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {} | + + Scenario: Add an asset in a property + Given I am in workspace "user-workspace" + Then the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | originDimensionSpacePoint | {} | + | propertyValues | {"asset": "Asset:asset-3"} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-david-nodenborough | asset-1 | asset | live | {} | + | sir-nodeward-nodington-iii | asset-3 | asset | user-workspace | {} | + | nody-mc-nodeface | asset-2 | assets | live | {} | + | nody-mc-nodeface | asset-3 | assets | live | {} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {} | + + Scenario: Add new asset property to the assets array + Given I am in workspace "user-workspace" + Then the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | nodeAggregateId | "nody-mc-nodeface" | + | originDimensionSpacePoint | {} | + | propertyValues | {"assets": ["Asset:asset-1", "Asset:asset-2", "Asset:asset-3"]} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-david-nodenborough | asset-1 | asset | live | {} | + | nody-mc-nodeface | asset-2 | assets | live | {} | + | nody-mc-nodeface | asset-3 | assets | live | {} | + | nody-mc-nodeface | asset-1 | assets | user-workspace | {} | + | nody-mc-nodeface | asset-2 | assets | user-workspace | {} | + | nody-mc-nodeface | asset-3 | assets | user-workspace | {} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {} | + + Scenario: Removes an asset entry from an assets array + Given I am in workspace "user-workspace" + Then the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | nodeAggregateId | "nody-mc-nodeface" | + | originDimensionSpacePoint | {} | + | propertyValues | {"assets": ["Asset:asset-3"]} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-david-nodenborough | asset-1 | asset | live | {} | + | nody-mc-nodeface | asset-2 | assets | live | {} | + | nody-mc-nodeface | asset-3 | assets | live | {} | + | nody-mc-nodeface | asset-3 | assets | user-workspace | {} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {} | diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature index 096f329bd1e..cd54c1f6abb 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature @@ -23,11 +23,6 @@ Feature: Publish nodes without dimensions | workspaceTitle | "Live" | | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | - And the command CreateWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-test" | - | baseWorkspaceName | "live" | - | newContentStreamId | "user-cs-id" | And I am in workspace "live" And I am in dimension space point {} @@ -42,31 +37,96 @@ Feature: Publish nodes without dimensions And an asset exists with id "asset-3" Scenario: Publish nodes from user workspace to live - Given I am in workspace "user-test" + Given the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + And I am in workspace "user-workspace" And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-test" | + | Key | Value | + | workspaceName | "user-workspace" | And I am in dimension space point {} Then the following CreateNodeAggregateWithNode commands are executed: - | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | - | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | - | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | - | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | And I expect the AssetUsageProjection to have the following AssetUsages: - | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | - | asset-1 | sir-david-nodenborough | asset | user-test | {} | - | asset-2 | nody-mc-nodeface | assets | user-test | {} | - | asset-3 | sir-nodeward-nodington-iii | text | user-test | {} | + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {} | And the command PublishWorkspace is executed with payload: | Key | Value | - | workspaceName | "user-test" | + | workspaceName | "user-workspace" | | newContentStreamId | "new-user-cs-id" | And I expect the AssetUsageProjection to have the following AssetUsages: | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | | asset-1 | sir-david-nodenborough | asset | live | {} | | asset-2 | nody-mc-nodeface | assets | live | {} | - | asset-3 | sir-nodeward-nodington-iii | text | live | {} | \ No newline at end of file + | asset-3 | sir-nodeward-nodington-iii | text | live | {} | + + Scenario: Publish nodes from user workspace to a non live workspace + Given the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "review-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "review-workspace-cs-id" | + + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "review-workspace" | + + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "review-workspace" | + | newContentStreamId | "user-workspace-cs-id" | + + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + And I am in workspace "user-workspace" + + And I am in dimension space point {} + Then the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {} | + + And the command PublishWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | newContentStreamId | "new-user-workspace-cs-id" | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | review-workspace | {} | + | asset-2 | nody-mc-nodeface | assets | review-workspace | {} | + | asset-3 | sir-nodeward-nodington-iii | text | review-workspace | {} | + + And the command PublishWorkspace is executed with payload: + | Key | Value | + | workspaceName | "review-workspace" | + | newContentStreamId | "new-review-workspace-cs-id" | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {} | + | asset-2 | nody-mc-nodeface | assets | live | {} | + | asset-3 | sir-nodeward-nodington-iii | text | live | {} | From f60b7344803583cd3710b49e36612080e17fb9f9 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Wed, 5 Jun 2024 18:08:57 +0200 Subject: [PATCH 07/13] TASK: AssetUsage projection stores own workspace chain, which allows handling new edges. Also remove unset properties --- .../Projection/AssetUsageProjection.php | 64 +++++-- .../Projection/AssetUsageRepository.php | 157 ++++++++++++++---- 2 files changed, 177 insertions(+), 44 deletions(-) diff --git a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php index b3ec5087f77..0526b44da54 100644 --- a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php +++ b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php @@ -12,7 +12,12 @@ use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; use Neos\ContentRepository\Core\Feature\NodeModification\Event\NodePropertiesWereSet; use Neos\ContentRepository\Core\Feature\NodeRemoval\Event\NodeAggregateWasRemoved; +use Neos\ContentRepository\Core\Feature\NodeVariation\Event\NodeGeneralizationVariantWasCreated; use Neos\ContentRepository\Core\Feature\NodeVariation\Event\NodePeerVariantWasCreated; +use Neos\ContentRepository\Core\Feature\NodeVariation\Event\NodeSpecializationVariantWasCreated; +use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Event\RootWorkspaceWasCreated; +use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Event\WorkspaceWasCreated; +use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceBaseWorkspaceWasChanged; use Neos\ContentRepository\Core\Feature\WorkspaceModification\Event\WorkspaceWasRemoved; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasDiscarded; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Event\WorkspaceWasPartiallyDiscarded; @@ -112,6 +117,8 @@ public function whenNodePropertiesWereSet(NodePropertiesWereSet $event, EventEnv $event->getOriginDimensionSpacePoint()->toDimensionSpacePoint(), $event->nodeAggregateId ); + + $this->repository->deleteAssetUsageByNodeAddressAndPropertyNames($nodeAddress, $event->propertiesToUnset); $this->repository->addUsagesForNode($nodeAddress, $assetIdsByProperty); } @@ -124,15 +131,24 @@ public function whenNodeAggregateWasRemoved(NodeAggregateWasRemoved $event): voi ); } - public function whenNodePeerVariantWasCreated(NodePeerVariantWasCreated $event): void { - $this->repository->copyDimensions($event->sourceOrigin, $event->peerOrigin); + $this->repository->copyNodeAggregateFromBaseWorkspace($event->nodeAggregateId, $event->workspaceName, $event->sourceOrigin, $event->peerOrigin); + } + + public function whenNodeGeneralizationVariantWasCreated(NodeGeneralizationVariantWasCreated $event): void + { + $this->repository->copyNodeAggregateFromBaseWorkspace($event->nodeAggregateId, $event->workspaceName, $event->sourceOrigin, $event->generalizationOrigin); + } + + public function whenNodeSpecializationVariantWasCreated(NodeSpecializationVariantWasCreated $event): void + { + $this->repository->copyNodeAggregateFromBaseWorkspace($event->nodeAggregateId, $event->workspaceName, $event->sourceOrigin, $event->specializationOrigin); } public function whenWorkspaceWasDiscarded(WorkspaceWasDiscarded $event): void { - $this->repository->removeWorkspaceName($event->workspaceName); + $this->repository->removeByWorkspaceName($event->workspaceName); } public function whenWorkspaceWasPartiallyDiscarded(WorkspaceWasPartiallyDiscarded $event): void @@ -159,17 +175,34 @@ public function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublishe public function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void { - $this->repository->removeWorkspaceName($event->sourceWorkspaceName); + $this->repository->removeByWorkspaceName($event->sourceWorkspaceName); } public function whenWorkspaceWasRebased(WorkspaceWasRebased $event): void { - $this->repository->removeWorkspaceName($event->workspaceName); + $this->repository->removeByWorkspaceName($event->workspaceName); } public function whenWorkspaceWasRemoved(WorkspaceWasRemoved $event): void { - $this->repository->removeWorkspaceName($event->workspaceName); + $this->repository->removeByWorkspaceName($event->workspaceName); + $this->repository->removeWorkspace($event->workspaceName); + } + + public function whenWorkspaceWasCreated(WorkspaceWasCreated $event): void + { + $this->repository->addWorkspace($event->workspaceName, $event->baseWorkspaceName); + } + + private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): void + { + $this->repository->addWorkspace($event->workspaceName, null); + } + + private function whenWorkspaceBaseWorkspaceWasChanged(WorkspaceBaseWorkspaceWasChanged $event): void + { + $this->repository->updateWorkspace($event->workspaceName, $event->baseWorkspaceName); + } @@ -265,14 +298,17 @@ public function canHandle(EventInterface $event): bool NodePropertiesWereSet::class, NodeAggregateWasRemoved::class, NodePeerVariantWasCreated::class, - // NodeGeneralizationVariantWasCreated::class, - // NodeSpecializationVariantWasCreated::class, + NodeGeneralizationVariantWasCreated::class, + NodeSpecializationVariantWasCreated::class, WorkspaceWasDiscarded::class, WorkspaceWasPartiallyDiscarded::class, WorkspaceWasPartiallyPublished::class, WorkspaceWasPublished::class, WorkspaceWasRebased::class, - WorkspaceWasRemoved::class + WorkspaceWasRemoved::class, + WorkspaceWasCreated::class, + RootWorkspaceWasCreated::class, + WorkspaceBaseWorkspaceWasChanged::class, ]); } @@ -283,15 +319,17 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void NodePropertiesWereSet::class => $this->whenNodePropertiesWereSet($event, $eventEnvelope), NodeAggregateWasRemoved::class => $this->whenNodeAggregateWasRemoved($event), NodePeerVariantWasCreated::class => $this->whenNodePeerVariantWasCreated($event), - // TODO - // NodeGeneralizationVariantWasCreated::class => "", - // NodeSpecializationVariantWasCreated::class => "", + NodeGeneralizationVariantWasCreated::class => $this->whenNodeGeneralizationVariantWasCreated($event), + NodeSpecializationVariantWasCreated::class => $this->whenNodeSpecializationVariantWasCreated($event), WorkspaceWasDiscarded::class => $this->whenWorkspaceWasDiscarded($event), WorkspaceWasPartiallyDiscarded::class => $this->whenWorkspaceWasPartiallyDiscarded($event), WorkspaceWasPartiallyPublished::class => $this->whenWorkspaceWasPartiallyPublished($event), WorkspaceWasPublished::class => $this->whenWorkspaceWasPublished($event), WorkspaceWasRebased::class => $this->whenWorkspaceWasRebased($event), WorkspaceWasRemoved::class => $this->whenWorkspaceWasRemoved($event), + WorkspaceWasCreated::class => $this->whenWorkspaceWasCreated($event), + RootWorkspaceWasCreated::class => $this->whenRootWorkspaceWasCreated($event), + WorkspaceBaseWorkspaceWasChanged::class => $this->whenWorkspaceBaseWorkspaceWasChanged($event), default => throw new \InvalidArgumentException(sprintf('Unsupported event %s', get_debug_type($event))), }; } @@ -318,7 +356,7 @@ private function findOriginalAssetId(string $assetId): ?string } /** @noinspection PhpRedundantCatchClauseInspection */ catch (ORMException) { return null; } - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore-next-line */ $this->originalAssetIdMappingRuntimeCache[$assetId] = $asset instanceof AssetVariantInterface ? $asset->getOriginalAsset()->getIdentifier() : null; } diff --git a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php index c9e069bb98e..05d696b6984 100644 --- a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php +++ b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php @@ -20,6 +20,8 @@ use Neos\ContentRepository\Core\Infrastructure\DbalSchemaDiff; use Neos\ContentRepository\Core\Infrastructure\DbalSchemaFactory; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Node\PropertyName; +use Neos\ContentRepository\Core\SharedModel\Node\PropertyNames; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Neos\AssetUsage\Dto\AssetIdAndOriginalAssetId; use Neos\Neos\AssetUsage\Dto\AssetIdsByProperty; @@ -64,7 +66,7 @@ private function databaseSchema(): Schema if (!$schemaManager instanceof AbstractSchemaManager) { throw new \RuntimeException('Failed to retrieve Schema Manager', 1625653914); } - $table = new Table($this->tableNamePrefix, [ + $assetUsageTable = new Table($this->tableNamePrefix, [ (new Column('assetid', Type::getType(Types::STRING)))->setLength(40)->setNotnull(true)->setDefault(''), (new Column('originalassetid', Type::getType(Types::STRING)))->setLength(40)->setNotnull(false)->setDefault(null), DbalSchemaFactory::columnForWorkspaceName('workspacename')->setNotNull(true), @@ -74,7 +76,7 @@ private function databaseSchema(): Schema (new Column('propertyname', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setDefault('') ]); - $table + $assetUsageTable ->addUniqueIndex(['assetid', 'originalassetid', 'workspacename', 'nodeaggregateid', 'origindimensionspacepointhash', 'propertyname'], 'assetperproperty') ->addIndex(['assetid']) ->addIndex(['originalassetid']) @@ -82,7 +84,18 @@ private function databaseSchema(): Schema ->addIndex(['nodeaggregateid']) ->addIndex(['origindimensionspacepointhash']); - return DbalSchemaFactory::createSchemaWithTables($schemaManager, [$table]); + + $workspaceChainTable = new Table($this->getWorkspacesTableName(), [ + DbalSchemaFactory::columnForWorkspaceName('workspacename')->setNotNull(true), + DbalSchemaFactory::columnForWorkspaceName('baseworkspacename')->setNotNull(false), + ]); + + $workspaceChainTable + ->addUniqueIndex(['workspacename', 'baseworkspacename'], 'workspacerelation') + ->addIndex(['workspacename']) + ->addIndex(['baseworkspacename']); + + return DbalSchemaFactory::createSchemaWithTables($schemaManager, [$assetUsageTable, $workspaceChainTable]); } public function findUsages(AssetUsageFilter $filter): AssetUsages @@ -148,18 +161,7 @@ public function findUsages(AssetUsageFilter $filter): AssetUsages public function addUsagesForNode(AssetUsageNodeAddress $nodeAddress, AssetIdsByProperty $assetIdsByProperty): void { // Delete all asset usage entries for newly set properties to ensure that removed or replaced assets are reflected - $this->dbal->executeStatement('DELETE FROM ' . $this->tableNamePrefix - . ' WHERE workspacename = :workspaceName' - . ' AND nodeaggregateid = :nodeAggregateId' - . ' AND origindimensionspacepointhash = :originDimensionSpacePointHash' - . ' AND propertyname IN (:propertyNames)', [ - 'workspaceName' => $nodeAddress->workspaceName->value, - 'nodeAggregateId' => $nodeAddress->nodeAggregateId->value, - 'originDimensionSpacePointHash' => $nodeAddress->dimensionSpacePoint->hash, - 'propertyNames' => $assetIdsByProperty->propertyNames(), - ], [ - 'propertyNames' => Connection::PARAM_STR_ARRAY, - ]); + $this->deleteAssetUsageByNodeAddressAndPropertyNames($nodeAddress, PropertyNames::fromArray($assetIdsByProperty->propertyNames())); foreach ($assetIdsByProperty as $propertyName => $assetIdAndOriginalAssetIds) { /** @var AssetIdAndOriginalAssetId $assetIdAndOriginalAssetId */ @@ -181,29 +183,60 @@ public function addUsagesForNode(AssetUsageNodeAddress $nodeAddress, AssetIdsByP } } - public function removeWorkspaceName(WorkspaceName $workspaceName): void + public function deleteAssetUsageByNodeAddressAndPropertyNames(AssetUsageNodeAddress $nodeAddress, PropertyNames $propertyNames): void { - $this->dbal->delete($this->tableNamePrefix, ['workspaceName' => $workspaceName->value]); + $this->dbal->executeStatement('DELETE FROM ' . $this->tableNamePrefix + . ' WHERE workspacename = :workspaceName' + . ' AND nodeaggregateid = :nodeAggregateId' + . ' AND origindimensionspacepointhash = :originDimensionSpacePointHash' + . ' AND propertyname IN (:propertyNames)', [ + 'workspaceName' => $nodeAddress->workspaceName->value, + 'nodeAggregateId' => $nodeAddress->nodeAggregateId->value, + 'originDimensionSpacePointHash' => $nodeAddress->dimensionSpacePoint->hash, + 'propertyNames' => array_map(static fn(PropertyName $propertyName) => $propertyName->value, iterator_to_array($propertyNames)), + ], [ + 'propertyNames' => Connection::PARAM_STR_ARRAY, + ]); + } + + public function removeByWorkspaceName(WorkspaceName $workspaceName): void + { + $this->dbal->delete($this->tableNamePrefix, ['workspacename' => $workspaceName->value]); } - public function copyDimensions( + public function copyNodeAggregateFromBaseWorkspace( + NodeAggregateId $nodeAggregateId, + WorkspaceName $workspaceName, OriginDimensionSpacePoint $sourceOriginDimensionSpacePoint, OriginDimensionSpacePoint $targetOriginDimensionSpacePoint, ): void { try { - $this->dbal->executeStatement( - 'INSERT INTO ' . $this->tableNamePrefix . ' (assetid, originalassetid, workspacename, nodeaggregateid, origindimensionspacepoint, origindimensionspacepointhash, propertyname)' - . ' SELECT assetid, originalassetid, workspacename, nodeaggregateid,' - . ' :targetOriginDimensionSpacePoint AS origindimensionspacepoint,' - . ' :targetOriginDimensionSpacePointHash AS origindimensionspacepointhash, propertyname' - . ' FROM ' . $this->tableNamePrefix - . ' WHERE originDimensionSpacePointHash = :sourceOriginDimensionSpacePointHash', - [ - 'sourceOriginDimensionSpacePointHash' => $sourceOriginDimensionSpacePoint->hash, - 'targetOriginDimensionSpacePoint' => $targetOriginDimensionSpacePoint->toJson(), - 'targetOriginDimensionSpacePointHash' => $targetOriginDimensionSpacePoint->hash, - ] - ); + $baseWorkspaces = $this->getBaseWorkspaces($workspaceName); + foreach ($baseWorkspaces as $baseWorkspace) { + $affectedRows = $this->dbal->executeStatement( + 'INSERT INTO ' . $this->tableNamePrefix . ' (assetid, originalassetid, workspacename, nodeaggregateid, origindimensionspacepoint, origindimensionspacepointhash, propertyname)' + . ' SELECT assetid, originalassetid, :workspaceName, nodeaggregateid,' + . ' :targetOriginDimensionSpacePoint AS origindimensionspacepoint,' + . ' :targetOriginDimensionSpacePointHash AS origindimensionspacepointhash, propertyname' + . ' FROM ' . $this->tableNamePrefix + . ' WHERE originDimensionSpacePointHash = :sourceOriginDimensionSpacePointHash' + . ' AND nodeaggregateid = :nodeAggregateId' + . ' AND workspacename = :baseWorkspaceName', + [ + 'nodeAggregateId' => $nodeAggregateId->value, + 'workspaceName' => $workspaceName->value, + 'baseWorkspaceName' => $baseWorkspace->value, + 'sourceOriginDimensionSpacePointHash' => $sourceOriginDimensionSpacePoint->hash, + 'targetOriginDimensionSpacePoint' => $targetOriginDimensionSpacePoint->toJson(), + 'targetOriginDimensionSpacePointHash' => $targetOriginDimensionSpacePoint->hash, + ] + ); + + if ($affectedRows > 0) { + // We found a baseWorkspace with an assetUsage + return; + } + } } catch (UniqueConstraintViolationException $e) { // A usage already exists for this node and property -> can be ignored } @@ -248,6 +281,62 @@ public function removeNodeInWorkspace( ); } + public function addWorkspace(WorkspaceName $workspaceName, ?WorkspaceName $baseWorkspaceName): void + { + $this->dbal->insert( + $this->getWorkspacesTableName(), + [ + 'workspacename' => $workspaceName->value, + 'baseworkspacename' => $baseWorkspaceName?->value, + ] + ); + } + + public function updateWorkspace(WorkspaceName $workspaceName, ?WorkspaceName $baseWorkspaceName): void + { + $this->dbal->update( + $this->getWorkspacesTableName(), + [ + 'baseworkspacename' => $baseWorkspaceName?->value, + ], + [ + 'workspacename' => $workspaceName->value, + ], + ); + } + + public function removeWorkspace(WorkspaceName $workspaceName): void + { + $this->dbal->delete( + $this->getWorkspacesTableName(), + [ + 'workspacename' => $workspaceName->value, + ] + ); + } + + /** + * @param WorkspaceName $workspaceName + * @return array + */ + public function getBaseWorkspaces(WorkspaceName $workspaceName): array + { + $baseWorkspaces = $this->dbal->executeQuery( + 'WITH RECURSIVE workspaceChain AS (' + . 'SELECT * FROM ' . $this->getWorkspacesTableName() . ' w WHERE w.workspacename = :workspaceName' + . ' UNION' + . ' SELECT w.workspacename, w.baseworkspacename from ' . $this->getWorkspacesTableName() . ' w' + . ' INNER JOIN workspaceChain ON workspaceChain.baseworkspacename = w.workspacename' + . ' )' + . 'SELECT baseworkspacename FROM workspaceChain WHERE baseworkspacename is not null ;', + [ + 'workspaceName' => $workspaceName->value + ] + )->fetchFirstColumn(); + + return array_map(WorkspaceName::fromString(...), $baseWorkspaces); + } + /** * @throws DbalException */ @@ -265,10 +354,16 @@ public function reset(): void ); } $this->dbal->executeStatement($platform->getTruncateTableSQL($this->tableNamePrefix)); + $this->dbal->executeStatement($platform->getTruncateTableSQL($this->getWorkspacesTableName())); } public function getTableNamePrefix(): string { return $this->tableNamePrefix; } + + private function getWorkspacesTableName(): string + { + return $this->tableNamePrefix . '_workspaces'; + } } From 03e597aece7b03a65a4af199511c317e609206d3 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Wed, 5 Jun 2024 18:09:31 +0200 Subject: [PATCH 08/13] TASK: Group assetUsage by node --- Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index bc06a3d6538..55a7d5953c9 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -277,10 +277,10 @@ public function registerAssetChange(AssetInterface $asset): void $tagsToFlush = []; $filter = AssetUsageFilter::create() + ->groupByNode() ->withAsset($this->persistenceManager->getIdentifierByObject($asset)) ->includeVariantsOfAsset(); - foreach ($this->globalAssetUsageService->findByFilter($filter) as $contentRepositoryId => $usages) { foreach ($usages as $usage) { $contentRepository = $this->contentRepositoryRegistry->get(ContentRepositoryId::fromString($contentRepositoryId)); From c0bfa9ad3845e5a8110466ca4f29bec2063848af Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Wed, 5 Jun 2024 18:09:53 +0200 Subject: [PATCH 09/13] TASK: Improve tests for assetUsage --- ...ggregateWithNode_WithoutDimensions.feature | 67 ++++------------ .../01-CreateNodePeerVariant.feature | 71 ++++++++++++++++ ...eNodePeerVariant_InternalWorkspace.feature | 80 +++++++++++++++++++ ...etNodeProperties_WithoutDimensions.feature | 4 +- ...PublishWorkspace_WithoutDimensions.feature | 11 --- 5 files changed, 167 insertions(+), 66 deletions(-) create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/01-CreateNodePeerVariant.feature create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/02-CreateNodePeerVariant_InternalWorkspace.feature rename Neos.Neos/Tests/Behavior/Features/AssetUsage/{02-NodeModification => 03-NodeModification}/01-SetNodeProperties_WithoutDimensions.feature (98%) diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature index 69ecb137c20..c486d7c2e79 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature @@ -1,6 +1,6 @@ @contentrepository @adapters=DoctrineDBAL @flowEntities -Feature: Publish nodes without dimensions +Feature: Create node aggregate with node Background: Given using no content dimensions @@ -36,82 +36,43 @@ Feature: Publish nodes without dimensions And an asset exists with id "asset-2" And an asset exists with id "asset-3" - Scenario: Publish nodes from user workspace to live - Given the command CreateWorkspace is executed with payload: + When the command CreateWorkspace is executed with payload: | Key | Value | | workspaceName | "user-workspace" | | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-id" | - And I am in workspace "user-workspace" And the command RebaseWorkspace is executed with payload: | Key | Value | | workspaceName | "user-workspace" | And I am in dimension space point {} - Then the following CreateNodeAggregateWithNode commands are executed: - | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | - | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | - | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | - | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | - | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + Scenario: Nodes on live workspace have been created + Given I am in workspace "live" - And I expect the AssetUsageProjection to have the following AssetUsages: - | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | - | asset-1 | sir-david-nodenborough | asset | user-workspace | {} | - | asset-2 | nody-mc-nodeface | assets | user-workspace | {} | - | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {} | + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | - And I expect the AssetUsageProjection to have the following AssetUsages: + Then I expect the AssetUsageProjection to have the following AssetUsages: | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | | asset-1 | sir-david-nodenborough | asset | live | {} | | asset-2 | nody-mc-nodeface | assets | live | {} | | asset-3 | sir-nodeward-nodington-iii | text | live | {} | - Scenario: Publish nodes from user workspace to a non live workspace - Given the command CreateWorkspace is executed with payload: - | Key | Value | - | workspaceName | "review-workspace" | - | baseWorkspaceName | "live" | - | newContentStreamId | "review-workspace-cs-id" | - - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "review-workspace" | - - And the command CreateWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - | baseWorkspaceName | "review-workspace" | - | newContentStreamId | "user-workspace-cs-id" | + Scenario: Nodes on user workspace have been created + Given I am in workspace "user-workspace" - And the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-workspace" | - - And I am in workspace "user-workspace" - - And I am in dimension space point {} - Then the following CreateNodeAggregateWithNode commands are executed: + And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | - And I expect the AssetUsageProjection to have the following AssetUsages: + Then I expect the AssetUsageProjection to have the following AssetUsages: | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | | asset-1 | sir-david-nodenborough | asset | user-workspace | {} | | asset-2 | nody-mc-nodeface | assets | user-workspace | {} | | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {} | - - And I expect the AssetUsageProjection to have the following AssetUsages: - | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | - | asset-1 | sir-david-nodenborough | asset | review-workspace | {} | - | asset-2 | nody-mc-nodeface | assets | review-workspace | {} | - | asset-3 | sir-nodeward-nodington-iii | text | review-workspace | {} | - - And I expect the AssetUsageProjection to have the following AssetUsages: - | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | - | asset-1 | sir-david-nodenborough | asset | live | {} | - | asset-2 | nody-mc-nodeface | assets | live | {} | - | asset-3 | sir-nodeward-nodington-iii | text | live | {} | diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/01-CreateNodePeerVariant.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/01-CreateNodePeerVariant.feature new file mode 100644 index 00000000000..7cf2f145867 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/01-CreateNodePeerVariant.feature @@ -0,0 +1,71 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Create node peer variant + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | language | de,gsw,fr | gsw->de, fr | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + + And I am in workspace "live" + And I am in dimension space point {"language": "de"} + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"], "asset": "Asset:asset-1"} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + + Then the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + Scenario: Create node peer variant of node with asset in property + When I am in workspace "user-workspace" and dimension space point {"language":"de"} + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | sourceOrigin | {"language":"de"} | + | targetOrigin | {"language":"gsw"} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | + | asset-1 | nody-mc-nodeface | asset | live | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | live | {"language": "de"} | + | asset-1 | nody-mc-nodeface | asset | user-workspace | {"language": "gsw"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "gsw"} | + | asset-3 | sir-nodeward-nodington-iii | text | live | {"language": "de"} | \ No newline at end of file diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/02-CreateNodePeerVariant_InternalWorkspace.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/02-CreateNodePeerVariant_InternalWorkspace.feature new file mode 100644 index 00000000000..847354118d8 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/02-CreateNodePeerVariant_InternalWorkspace.feature @@ -0,0 +1,80 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Create node peer variant with internal workspace between live and user workspace + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | language | de,gsw,fr | gsw->de, fr | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "internal-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "internal-cs-id" | + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "internal-workspace" | + | newContentStreamId | "user-cs-id" | + + And I am in workspace "live" + And I am in dimension space point {"language": "de"} + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"], "asset": "Asset:asset-1"} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + + Then the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "internal-workspace" | + + Then the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + Scenario: Create node peer variant of node with asset in property + When I am in workspace "user-workspace" and dimension space point {"language":"de"} + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | sourceOrigin | {"language":"de"} | + | targetOrigin | {"language":"gsw"} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | + | asset-1 | nody-mc-nodeface | asset | live | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | live | {"language": "de"} | + | asset-1 | nody-mc-nodeface | asset | user-workspace | {"language": "gsw"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "gsw"} | + | asset-3 | sir-nodeward-nodington-iii | text | live | {"language": "de"} | \ No newline at end of file diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeModification/01-SetNodeProperties_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/03-NodeModification/01-SetNodeProperties_WithoutDimensions.feature similarity index 98% rename from Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeModification/01-SetNodeProperties_WithoutDimensions.feature rename to Neos.Neos/Tests/Behavior/Features/AssetUsage/03-NodeModification/01-SetNodeProperties_WithoutDimensions.feature index 684d2517cfb..fb0e8adef2c 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeModification/01-SetNodeProperties_WithoutDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/03-NodeModification/01-SetNodeProperties_WithoutDimensions.feature @@ -96,12 +96,12 @@ Feature: Create node aggregate with node | workspaceName | "live" | | nodeAggregateId | "sir-david-nodenborough" | | originDimensionSpacePoint | {} | - | propertyValues | {} | - | propertiesToUnset | ["asset"] | + | propertyValues | {"asset": null} | Then I expect the AssetUsageProjection to have the following AssetUsages: | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | | nody-mc-nodeface | asset-2 | assets | live | {} | + | nody-mc-nodeface | asset-3 | assets | live | {} | | sir-nodeward-nodington-iii | asset-3 | text | live | {} | Scenario: Add an asset in a property diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature index cd54c1f6abb..2c36cc6c67f 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/01-PublishWorkspace_WithoutDimensions.feature @@ -119,14 +119,3 @@ Feature: Publish nodes without dimensions | asset-1 | sir-david-nodenborough | asset | review-workspace | {} | | asset-2 | nody-mc-nodeface | assets | review-workspace | {} | | asset-3 | sir-nodeward-nodington-iii | text | review-workspace | {} | - - And the command PublishWorkspace is executed with payload: - | Key | Value | - | workspaceName | "review-workspace" | - | newContentStreamId | "new-review-workspace-cs-id" | - - And I expect the AssetUsageProjection to have the following AssetUsages: - | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | - | asset-1 | sir-david-nodenborough | asset | live | {} | - | asset-2 | nody-mc-nodeface | assets | live | {} | - | asset-3 | sir-nodeward-nodington-iii | text | live | {} | From 1faaffb7e91e9e9c7d5d7570cf7798c7e2c5dfdd Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sun, 16 Jun 2024 23:00:13 +0200 Subject: [PATCH 10/13] TASK: Improve tests for assetUsage --- .../Projection/AssetUsageProjection.php | 6 +- .../Projection/AssetUsageRepository.php | 4 +- ...ggregateWithNode_WithoutDimensions.feature | 2 +- ...deAggregateWithNode_WithDimensions.feature | 89 ++++++++ ...01-CreateNodeGeneralizationVariant.feature | 83 ++++++++ ...2-CreateNodeSpecializationVariant.feature} | 6 +- ...lizationVariant_InternalWorkspace.feature} | 0 .../04-CreateNodePeerVariant.feature | 70 +++++++ ...etNodeProperties_WithoutDimensions.feature | 2 +- ...2-SetNodeProperties_WithDimensions.feature | 165 +++++++++++++++ ...02-PublishWorkspace_WithDimensions.feature | 191 ++++++++++++++++++ 11 files changed, 608 insertions(+), 10 deletions(-) create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/02-CreateNodeAggregateWithNode_WithDimensions.feature create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/01-CreateNodeGeneralizationVariant.feature rename Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/{01-CreateNodePeerVariant.feature => 02-CreateNodeSpecializationVariant.feature} (94%) rename Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/{02-CreateNodePeerVariant_InternalWorkspace.feature => 03-CreateNodeSpecializationVariant_InternalWorkspace.feature} (100%) create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/04-CreateNodePeerVariant.feature create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/03-NodeModification/02-SetNodeProperties_WithDimensions.feature create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/02-PublishWorkspace_WithDimensions.feature diff --git a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php index 0526b44da54..404cf1147fe 100644 --- a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php +++ b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php @@ -316,11 +316,11 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void { match ($event::class) { NodeAggregateWithNodeWasCreated::class => $this->whenNodeAggregateWithNodeWasCreated($event, $eventEnvelope), - NodePropertiesWereSet::class => $this->whenNodePropertiesWereSet($event, $eventEnvelope), - NodeAggregateWasRemoved::class => $this->whenNodeAggregateWasRemoved($event), - NodePeerVariantWasCreated::class => $this->whenNodePeerVariantWasCreated($event), NodeGeneralizationVariantWasCreated::class => $this->whenNodeGeneralizationVariantWasCreated($event), NodeSpecializationVariantWasCreated::class => $this->whenNodeSpecializationVariantWasCreated($event), + NodePeerVariantWasCreated::class => $this->whenNodePeerVariantWasCreated($event), + NodePropertiesWereSet::class => $this->whenNodePropertiesWereSet($event, $eventEnvelope), + NodeAggregateWasRemoved::class => $this->whenNodeAggregateWasRemoved($event), WorkspaceWasDiscarded::class => $this->whenWorkspaceWasDiscarded($event), WorkspaceWasPartiallyDiscarded::class => $this->whenWorkspaceWasPartiallyDiscarded($event), WorkspaceWasPartiallyPublished::class => $this->whenWorkspaceWasPartiallyPublished($event), diff --git a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php index 05d696b6984..b8303820b8a 100644 --- a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php +++ b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php @@ -211,8 +211,8 @@ public function copyNodeAggregateFromBaseWorkspace( OriginDimensionSpacePoint $targetOriginDimensionSpacePoint, ): void { try { - $baseWorkspaces = $this->getBaseWorkspaces($workspaceName); - foreach ($baseWorkspaces as $baseWorkspace) { + $workspaceChain = [$workspaceName, ...$this->getBaseWorkspaces($workspaceName)]; + foreach ($workspaceChain as $baseWorkspace) { $affectedRows = $this->dbal->executeStatement( 'INSERT INTO ' . $this->tableNamePrefix . ' (assetid, originalassetid, workspacename, nodeaggregateid, origindimensionspacepoint, origindimensionspacepointhash, propertyname)' . ' SELECT assetid, originalassetid, :workspaceName, nodeaggregateid,' diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature index c486d7c2e79..8ea954b627f 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/01-CreateNodeAggregateWithNode_WithoutDimensions.feature @@ -1,6 +1,6 @@ @contentrepository @adapters=DoctrineDBAL @flowEntities -Feature: Create node aggregate with node +Feature: Create node aggregate with node without dimensions Background: Given using no content dimensions diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/02-CreateNodeAggregateWithNode_WithDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/02-CreateNodeAggregateWithNode_WithDimensions.feature new file mode 100644 index 00000000000..2f0ca9dc196 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/01-NodeCreation/02-CreateNodeAggregateWithNode_WithDimensions.feature @@ -0,0 +1,89 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Create node aggregate with node with dimensions + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | language | de,gsw,fr | gsw->de, fr | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + + And I am in workspace "live" + And I am in dimension space point {"language": "de"} + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + When the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + And I am in dimension space point {"language": "de"} + + Scenario: Nodes on live workspace have been created + Given I am in workspace "live" + + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + + Then I am in dimension space point {"language": "fr"} + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {"language":"de"} | + | asset-2 | nody-mc-nodeface | assets | live | {"language":"de"} | + | asset-1 | sir-nodeward-nodington-iii | asset | live | {"language":"fr"} | + + Scenario: Nodes on user workspace have been created + Given I am in workspace "user-workspace" + + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + Then I am in dimension space point {"language": "fr"} + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {"language":"de"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language":"de"} | + | asset-1 | sir-nodeward-nodington-iii | asset | user-workspace | {"language":"fr"} | + diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/01-CreateNodeGeneralizationVariant.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/01-CreateNodeGeneralizationVariant.feature new file mode 100644 index 00000000000..5f87ad78350 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/01-CreateNodeGeneralizationVariant.feature @@ -0,0 +1,83 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Create node generalization variant + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | language | de,gsw,fr,en | gsw->de->en, fr | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + + And I am in workspace "live" + And I am in dimension space point {"language": "de"} + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"], "asset": "Asset:asset-1"} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + + Then the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + Scenario: Create node generalization variant of node with asset in property + When I am in workspace "user-workspace" and dimension space point {"language":"de"} + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | sourceOrigin | {"language":"de"} | + | targetOrigin | {"language":"en"} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {"language": "en"} | + | asset-1 | nody-mc-nodeface | asset | live | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | live | {"language": "de"} | + | asset-3 | sir-nodeward-nodington-iii | text | live | {"language": "de"} | + + And the command PublishWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | newContentStreamId | "new-user-workspace-cs-id" | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | + | asset-1 | sir-david-nodenborough | asset | live | {"language": "en"} | + | asset-1 | nody-mc-nodeface | asset | live | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | live | {"language": "de"} | + | asset-3 | sir-nodeward-nodington-iii | text | live | {"language": "de"} | \ No newline at end of file diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/01-CreateNodePeerVariant.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/02-CreateNodeSpecializationVariant.feature similarity index 94% rename from Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/01-CreateNodePeerVariant.feature rename to Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/02-CreateNodeSpecializationVariant.feature index 7cf2f145867..98a27d87f18 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/01-CreateNodePeerVariant.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/02-CreateNodeSpecializationVariant.feature @@ -1,6 +1,6 @@ @contentrepository @adapters=DoctrineDBAL @flowEntities -Feature: Create node peer variant +Feature: Create node specialization variant Background: Given using the following content dimensions: @@ -53,7 +53,7 @@ Feature: Create node peer variant | Key | Value | | workspaceName | "user-workspace" | - Scenario: Create node peer variant of node with asset in property + Scenario: Create node specialization variant of node with asset in property When I am in workspace "user-workspace" and dimension space point {"language":"de"} And the command CreateNodeVariant is executed with payload: | Key | Value | @@ -64,7 +64,7 @@ Feature: Create node peer variant Then I expect the AssetUsageProjection to have the following AssetUsages: | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | - | asset-1 | nody-mc-nodeface | asset | live | {"language": "de"} | + | asset-1 | nody-mc-nodeface | asset | live | {"language": "de"} | | asset-2 | nody-mc-nodeface | assets | live | {"language": "de"} | | asset-1 | nody-mc-nodeface | asset | user-workspace | {"language": "gsw"} | | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "gsw"} | diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/02-CreateNodePeerVariant_InternalWorkspace.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/03-CreateNodeSpecializationVariant_InternalWorkspace.feature similarity index 100% rename from Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/02-CreateNodePeerVariant_InternalWorkspace.feature rename to Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/03-CreateNodeSpecializationVariant_InternalWorkspace.feature diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/04-CreateNodePeerVariant.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/04-CreateNodePeerVariant.feature new file mode 100644 index 00000000000..a4037aaeffe --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/02-NodeVariation/04-CreateNodePeerVariant.feature @@ -0,0 +1,70 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Create node peer variant + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | language | de,gsw,fr | gsw->de, fr | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + + And I am in workspace "live" + And I am in dimension space point {"language": "de"} + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"], "asset": "Asset:asset-1"} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + + Then the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + Scenario: Create node peer variant of node with asset in property + When I am in workspace "user-workspace" and dimension space point {"language":"de"} + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | sourceOrigin | {"language":"de"} | + | targetOrigin | {"language":"fr"} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {"language": "fr"} | + | asset-1 | nody-mc-nodeface | asset | live | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | live | {"language": "de"} | + | asset-3 | sir-nodeward-nodington-iii | text | live | {"language": "de"} | \ No newline at end of file diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/03-NodeModification/01-SetNodeProperties_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/03-NodeModification/01-SetNodeProperties_WithoutDimensions.feature index fb0e8adef2c..3e042ec5419 100644 --- a/Neos.Neos/Tests/Behavior/Features/AssetUsage/03-NodeModification/01-SetNodeProperties_WithoutDimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/03-NodeModification/01-SetNodeProperties_WithoutDimensions.feature @@ -1,6 +1,6 @@ @contentrepository @adapters=DoctrineDBAL @flowEntities -Feature: Create node aggregate with node +Feature: Create node aggregate with node without dimensions Background: Create node aggregate with initial node Given using no content dimensions diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/03-NodeModification/02-SetNodeProperties_WithDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/03-NodeModification/02-SetNodeProperties_WithDimensions.feature new file mode 100644 index 00000000000..68fdf4ab23f --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/03-NodeModification/02-SetNodeProperties_WithDimensions.feature @@ -0,0 +1,165 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Create node aggregate with node with dimensions + + Background: Create node aggregate with initial node + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | language | de,gsw,fr | gsw->de, fr | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + + Then the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + And I am in workspace "live" + And I am in dimension space point {"language": "de"} + + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + Then the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2", "Asset:asset-3"]} | + + And I am in dimension space point {"language": "fr"} + + Then the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3"} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And I am in workspace "user-workspace" + + Then the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + And I am in dimension space point {"language": "de"} + + Scenario: Set node properties without dimension and publish in user workspace + Given the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {"language": "de"} | + | propertyValues | {"asset": "Asset:asset-2"} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-david-nodenborough | asset-1 | asset | live | {"language": "de"} | + | sir-david-nodenborough | asset-2 | asset | user-workspace | {"language": "de"} | + | nody-mc-nodeface | asset-2 | assets | live | {"language": "de"} | + | nody-mc-nodeface | asset-3 | assets | live | {"language": "de"} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {"language": "fr"} | + + Scenario: Remove an asset from an existing property + Given the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {"language": "de"} | + | propertyValues | {"asset": null} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-david-nodenborough | asset-1 | asset | live | {"language": "de"} | + | nody-mc-nodeface | asset-2 | assets | live | {"language": "de"} | + | nody-mc-nodeface | asset-3 | assets | live | {"language": "de"} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {"language": "fr"} | + + Scenario: Remove an asset from an existing property from the live workspaces + Given I am in workspace "live" + And the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "live" | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {"language": "de"} | + | propertyValues | {"asset": null} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | nody-mc-nodeface | asset-2 | assets | live | {"language": "de"} | + | nody-mc-nodeface | asset-3 | assets | live | {"language": "de"} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {"language": "fr"} | + + Scenario: Add an asset in a property + Given I am in workspace "user-workspace" + Then the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | originDimensionSpacePoint | {"language": "fr"} | + | propertyValues | {"asset": "Asset:asset-3"} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-david-nodenborough | asset-1 | asset | live | {"language": "de"} | + | sir-nodeward-nodington-iii | asset-3 | asset | user-workspace | {"language": "fr"} | + | nody-mc-nodeface | asset-2 | assets | live | {"language": "de"} | + | nody-mc-nodeface | asset-3 | assets | live | {"language": "de"} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {"language": "fr"} | + + Scenario: Add new asset property to the assets array + Given I am in workspace "user-workspace" + Then the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | nodeAggregateId | "nody-mc-nodeface" | + | originDimensionSpacePoint | {"language": "de"} | + | propertyValues | {"assets": ["Asset:asset-1", "Asset:asset-2", "Asset:asset-3"]} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-david-nodenborough | asset-1 | asset | live | {"language": "de"} | + | nody-mc-nodeface | asset-2 | assets | live | {"language": "de"} | + | nody-mc-nodeface | asset-3 | assets | live | {"language": "de"} | + | nody-mc-nodeface | asset-1 | assets | user-workspace | {"language": "de"} | + | nody-mc-nodeface | asset-2 | assets | user-workspace | {"language": "de"} | + | nody-mc-nodeface | asset-3 | assets | user-workspace | {"language": "de"} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {"language": "fr"} | + + Scenario: Removes an asset entry from an assets array + Given I am in workspace "user-workspace" + Then the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | nodeAggregateId | "nody-mc-nodeface" | + | originDimensionSpacePoint | {"language": "de"} | + | propertyValues | {"assets": ["Asset:asset-3"]} | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-david-nodenborough | asset-1 | asset | live | {"language": "de"} | + | nody-mc-nodeface | asset-2 | assets | live | {"language": "de"} | + | nody-mc-nodeface | asset-3 | assets | live | {"language": "de"} | + | nody-mc-nodeface | asset-3 | assets | user-workspace | {"language": "de"} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {"language": "fr"} | diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/02-PublishWorkspace_WithDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/02-PublishWorkspace_WithDimensions.feature new file mode 100644 index 00000000000..ca0e88202b9 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/02-PublishWorkspace_WithDimensions.feature @@ -0,0 +1,191 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Publish nodes with dimensions + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | language | de,gsw,fr,en | gsw->de->en, fr | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + + And I am in workspace "live" + And I am in dimension space point {"language": "de"} + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + Scenario: Publish nodes from user workspace to live + Given the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + And I am in workspace "user-workspace" + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + Then I am in dimension space point {"language": "de"} + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + + Then I am in dimension space point {"language": "fr"} + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "de"} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {"language": "fr"} | + + And the command PublishWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | newContentStreamId | "new-user-cs-id" | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | live | {"language": "de"} | + | asset-3 | sir-nodeward-nodington-iii | text | live | {"language": "fr"} | + + Scenario: Publish nodes from user workspace to a non live workspace + Given the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "review-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "review-workspace-cs-id" | + + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "review-workspace" | + + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "review-workspace" | + | newContentStreamId | "user-workspace-cs-id" | + + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + And I am in workspace "user-workspace" + + Then I am in dimension space point {"language": "de"} + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + + Then I am in dimension space point {"language": "gsw"} + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | sourceOrigin | {"language":"de"} | + | targetOrigin | {"language":"gsw"} | + And the command SetNodeProperties is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | originDimensionSpacePoint | {"language":"gsw"} | + | propertyValues | {"assets": ["Asset:asset-2", "Asset:asset-1"]} | + + And I am in dimension space point {"language": "fr"} + Then the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "de"} | + | asset-1 | nody-mc-nodeface | assets | user-workspace | {"language": "gsw"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "gsw"} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {"language": "fr"} | + + And the command PublishWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | newContentStreamId | "new-user-workspace-cs-id" | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | review-workspace | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | review-workspace | {"language": "de"} | + | asset-1 | nody-mc-nodeface | assets | review-workspace | {"language": "gsw"} | + | asset-2 | nody-mc-nodeface | assets | review-workspace | {"language": "gsw"} | + | asset-3 | sir-nodeward-nodington-iii | text | review-workspace | {"language": "fr"} | + + Scenario: Publish nodes from user workspace to live with new generalization + Given the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + And I am in workspace "user-workspace" + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + Then I am in dimension space point {"language": "de"} + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | sourceOrigin | {"language":"de"} | + | targetOrigin | {"language":"en"} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {"language": "de"} | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {"language": "en"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "de"} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {"language": "de"} | + + And the command PublishWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | newContentStreamId | "new-user-cs-id" | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | + | asset-1 | sir-david-nodenborough | asset | live | {"language": "en"} | + | asset-2 | nody-mc-nodeface | assets | live | {"language": "de"} | + | asset-3 | sir-nodeward-nodington-iii | text | live | {"language": "de"} | \ No newline at end of file From 2860d653627940799ef1fa49f6f65931c7022665 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sun, 16 Jun 2024 23:12:12 +0200 Subject: [PATCH 11/13] TASK: Improve tests for assetUsage --- .../Classes/AssetUsage/Projection/AssetUsageProjection.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php index 404cf1147fe..fbf2710226d 100644 --- a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php +++ b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageProjection.php @@ -202,10 +202,8 @@ private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): vo private function whenWorkspaceBaseWorkspaceWasChanged(WorkspaceBaseWorkspaceWasChanged $event): void { $this->repository->updateWorkspace($event->workspaceName, $event->baseWorkspaceName); - } - // ---------------- /** From b24e9da95cde2faa9fadd9d05de88587ff2a158f Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Fri, 12 Jul 2024 21:21:30 +0200 Subject: [PATCH 12/13] TASK: Improve tests for assetUsage --- ...etNodeProperties_WithoutDimensions.feature | 99 +++++++++ ...desFromWorkspace_WithoutDimensions.feature | 123 +++++++++++ ...lNodesFromWorkspace_WithDimensions.feature | 194 ++++++++++++++++++ 3 files changed, 416 insertions(+) create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/04-NodeRemoval/01-SetNodeProperties_WithoutDimensions.feature create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/03-PublishIndividualNodesFromWorkspace_WithoutDimensions.feature create mode 100644 Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/04-PublishIndividualNodesFromWorkspace_WithDimensions.feature diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/04-NodeRemoval/01-SetNodeProperties_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/04-NodeRemoval/01-SetNodeProperties_WithoutDimensions.feature new file mode 100644 index 00000000000..a1818235311 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/04-NodeRemoval/01-SetNodeProperties_WithoutDimensions.feature @@ -0,0 +1,99 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Remove node aggregate with node without dimensions + + Background: Create node aggregate with initial node + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + + Then the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + And I am in workspace "live" + And I am in dimension space point {} + + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + Then the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2", "Asset:asset-3"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3"} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And I am in workspace "user-workspace" + + Then the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + And I am in dimension space point {} + + Scenario: Remove node aggregate in user-workspace + And the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {} | + | nodeVariantSelectionStrategy | "allSpecializations" | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-david-nodenborough | asset-1 | asset | live | {} | + | nody-mc-nodeface | asset-2 | assets | live | {} | + | nody-mc-nodeface | asset-3 | assets | live | {} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {} | + + Scenario: Remove node aggregate in live workspace + And the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | workspaceName | "live" | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {} | + | nodeVariantSelectionStrategy | "allSpecializations" | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-david-nodenborough | asset-1 | asset | live | {} | + | sir-nodeward-nodington-iii | asset-3 | text | live | {} | + + Scenario: Remove node aggregate with children in live workspace + And the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | workspaceName | "live" | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {} | + | nodeVariantSelectionStrategy | "allSpecializations" | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | nodeAggregateId | assetId | propertyName | workspaceName | originDimensionSpacePoint | + | sir-nodeward-nodington-iii | asset-3 | text | live | {} | \ No newline at end of file diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/03-PublishIndividualNodesFromWorkspace_WithoutDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/03-PublishIndividualNodesFromWorkspace_WithoutDimensions.feature new file mode 100644 index 00000000000..eadd14514b7 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/03-PublishIndividualNodesFromWorkspace_WithoutDimensions.feature @@ -0,0 +1,123 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Publish nodes partially without dimensions + + Background: + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + + And I am in workspace "live" + And I am in dimension space point {} + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + Scenario: Publish nodes partially from user workspace to live + Given the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + And I am in workspace "user-workspace" + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + And I am in dimension space point {} + + Then the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {} | + + When the command PublishIndividualNodesFromWorkspace is executed with payload: + | Key | Value | + | nodesToPublish | [{"workspaceName": "user-workspace", "dimensionSpacePoint": {}, "nodeAggregateId": "sir-david-nodenborough"}] | + | contentStreamIdForRemainingPart | "user-cs-identifier-remaining" | + | contentStreamIdForMatchingPart | "user-cs-identifier-matching" | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {} | + + Scenario: Publish nodes partially from user workspace to a non live workspace + Given the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "review-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "review-workspace-cs-id" | + + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "review-workspace" | + + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "review-workspace" | + | newContentStreamId | "user-workspace-cs-id" | + + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + And I am in workspace "user-workspace" + + And I am in dimension space point {} + Then the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {} | + + When the command PublishIndividualNodesFromWorkspace is executed with payload: + | Key | Value | + | nodesToPublish | [{"workspaceName": "user-workspace", "dimensionSpacePoint": {}, "nodeAggregateId": "sir-david-nodenborough"}] | + | contentStreamIdForRemainingPart | "user-cs-identifier-remaining" | + | contentStreamIdForMatchingPart | "user-cs-identifier-matching" | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | review-workspace | {} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {} | diff --git a/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/04-PublishIndividualNodesFromWorkspace_WithDimensions.feature b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/04-PublishIndividualNodesFromWorkspace_WithDimensions.feature new file mode 100644 index 00000000000..b385b60981f --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/AssetUsage/W01-WorkspacePublication/04-PublishIndividualNodesFromWorkspace_WithDimensions.feature @@ -0,0 +1,194 @@ +@contentrepository @adapters=DoctrineDBAL +@flowEntities +Feature: Publish nodes partially with dimensions + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | language | de,gsw,fr,en | gsw->de->en, fr | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:NodeWithAssetProperties': + properties: + text: + type: string + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + + And I am in workspace "live" + And I am in dimension space point {"language": "de"} + And I am user identified by "initiating-user-identifier" + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + When an asset exists with id "asset-1" + And an asset exists with id "asset-2" + And an asset exists with id "asset-3" + + Scenario: Publish nodes partially from user workspace to live + Given the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + And I am in workspace "user-workspace" + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + Then I am in dimension space point {"language": "de"} + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + + Then I am in dimension space point {"language": "fr"} + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "de"} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {"language": "fr"} | + + When the command PublishIndividualNodesFromWorkspace is executed with payload: + | Key | Value | + | nodesToPublish | [{"workspaceName": "user-workspace", "dimensionSpacePoint": {"language": "de"}, "nodeAggregateId": "sir-david-nodenborough"}] | + | contentStreamIdForRemainingPart | "user-cs-identifier-remaining" | + | contentStreamIdForMatchingPart | "user-cs-identifier-matching" | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "de"} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {"language": "fr"} | + + Scenario: Publish nodes partially from user workspace to a non live workspace + Given the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "review-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "review-workspace-cs-id" | + + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "review-workspace" | + + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "review-workspace" | + | newContentStreamId | "user-workspace-cs-id" | + + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + And I am in workspace "user-workspace" + + Then I am in dimension space point {"language": "de"} + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + + Then I am in dimension space point {"language": "gsw"} + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | sourceOrigin | {"language":"de"} | + | targetOrigin | {"language":"gsw"} | + And the command SetNodeProperties is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | originDimensionSpacePoint | {"language":"gsw"} | + | propertyValues | {"assets": ["Asset:asset-2", "Asset:asset-1"]} | + + And I am in dimension space point {"language": "fr"} + Then the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "de"} | + | asset-1 | nody-mc-nodeface | assets | user-workspace | {"language": "gsw"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "gsw"} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {"language": "fr"} | + + When the command PublishIndividualNodesFromWorkspace is executed with payload: + | Key | Value | + | nodesToPublish | [{"workspaceName": "user-workspace", "dimensionSpacePoint": {"language": "de"}, "nodeAggregateId": "sir-david-nodenborough"}] | + | contentStreamIdForRemainingPart | "user-cs-identifier-remaining" | + | contentStreamIdForMatchingPart | "user-cs-identifier-matching" | + + Then I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | review-workspace | {"language": "de"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "de"} | + | asset-1 | nody-mc-nodeface | assets | user-workspace | {"language": "gsw"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "gsw"} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {"language": "fr"} | + + Scenario: Publish nodes partially from user workspace to live with new generalization + Given the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-id" | + And I am in workspace "user-workspace" + And the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-workspace" | + + Then I am in dimension space point {"language": "de"} + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | nodeTypeName | initialPropertyValues | + | sir-david-nodenborough | node | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"asset": "Asset:asset-1"} | + | nody-mc-nodeface | child-node | sir-david-nodenborough | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"assets": ["Asset:asset-2"]} | + | sir-nodeward-nodington-iii | esquire | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Link to asset://asset-3."} | + | sir-nodeward-nodington-iiii | bakura | lady-eleonode-rootford | Neos.ContentRepository.Testing:NodeWithAssetProperties | {"text": "Text Without Asset"} | + + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | sourceOrigin | {"language":"de"} | + | targetOrigin | {"language":"en"} | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {"language": "de"} | + | asset-1 | sir-david-nodenborough | asset | user-workspace | {"language": "en"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "de"} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {"language": "de"} | + + When the command PublishIndividualNodesFromWorkspace is executed with payload: + | Key | Value | + | nodesToPublish | [{"workspaceName": "user-workspace", "dimensionSpacePoint": {"language": "de"}, "nodeAggregateId": "sir-david-nodenborough"},{"workspaceName": "user-workspace", "dimensionSpacePoint": {"language": "en"}, "nodeAggregateId": "sir-david-nodenborough"}] | + | contentStreamIdForRemainingPart | "user-cs-identifier-remaining" | + | contentStreamIdForMatchingPart | "user-cs-identifier-matching" | + + And I expect the AssetUsageProjection to have the following AssetUsages: + | assetId | nodeAggregateId | propertyName | workspaceName | originDimensionSpacePoint | + | asset-1 | sir-david-nodenborough | asset | live | {"language": "de"} | + | asset-1 | sir-david-nodenborough | asset | live | {"language": "en"} | + | asset-2 | nody-mc-nodeface | assets | user-workspace | {"language": "de"} | + | asset-3 | sir-nodeward-nodington-iii | text | user-workspace | {"language": "de"} | \ No newline at end of file From 72703dfe3983ae557c3ff98eb2e596a17708361f Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Thu, 15 Aug 2024 11:21:51 +0200 Subject: [PATCH 13/13] TASK: Include all workspaces of workspaceChain --- .../AssetUsage/Projection/AssetUsageRepository.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php index b8303820b8a..62527d55c27 100644 --- a/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php +++ b/Neos.Neos/Classes/AssetUsage/Projection/AssetUsageRepository.php @@ -211,7 +211,7 @@ public function copyNodeAggregateFromBaseWorkspace( OriginDimensionSpacePoint $targetOriginDimensionSpacePoint, ): void { try { - $workspaceChain = [$workspaceName, ...$this->getBaseWorkspaces($workspaceName)]; + $workspaceChain = $this->getWorkspaceChain($workspaceName); foreach ($workspaceChain as $baseWorkspace) { $affectedRows = $this->dbal->executeStatement( 'INSERT INTO ' . $this->tableNamePrefix . ' (assetid, originalassetid, workspacename, nodeaggregateid, origindimensionspacepoint, origindimensionspacepointhash, propertyname)' @@ -319,16 +319,16 @@ public function removeWorkspace(WorkspaceName $workspaceName): void * @param WorkspaceName $workspaceName * @return array */ - public function getBaseWorkspaces(WorkspaceName $workspaceName): array + public function getWorkspaceChain(WorkspaceName $workspaceName): array { $baseWorkspaces = $this->dbal->executeQuery( 'WITH RECURSIVE workspaceChain AS (' . 'SELECT * FROM ' . $this->getWorkspacesTableName() . ' w WHERE w.workspacename = :workspaceName' . ' UNION' - . ' SELECT w.workspacename, w.baseworkspacename from ' . $this->getWorkspacesTableName() . ' w' + . ' SELECT w.workspacename from ' . $this->getWorkspacesTableName() . ' w' . ' INNER JOIN workspaceChain ON workspaceChain.baseworkspacename = w.workspacename' . ' )' - . 'SELECT baseworkspacename FROM workspaceChain WHERE baseworkspacename is not null ;', + . 'SELECT workspacename FROM workspaceChain;', [ 'workspaceName' => $workspaceName->value ]