Skip to content

Commit

Permalink
Merge branch '4609-multipleContentCollectionPublishing' into 4608-tet…
Browse files Browse the repository at this point in the history
…heredSiteChildrenUriPathSegment
  • Loading branch information
Bernhard Schmitt committed Oct 13, 2023
2 parents f51830a + 495d799 commit 041670b
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
@contentrepository @adapters=DoctrineDBAL
Feature: Individual node publication

Publishing an individual node works

Background:
Given using no content dimensions
And using the following node types:
"""yaml
'Neos.ContentRepository:Root': {}
'Neos.ContentRepository.Testing:Content': {}
'Neos.ContentRepository.Testing:Document':
childNodes:
child1:
type: 'Neos.ContentRepository.Testing:Content'
child2:
type: 'Neos.ContentRepository.Testing:Content'
"""
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" |
| newContentStreamId | "cs-identifier" |
And the graph projection is fully up to date
And the command CreateRootNodeAggregateWithNode is executed with payload:
| Key | Value |
| contentStreamId | "cs-identifier" |
| nodeAggregateId | "lady-eleonode-rootford" |
| nodeTypeName | "Neos.ContentRepository:Root" |

# Create user workspace
And the command CreateWorkspace is executed with payload:
| Key | Value |
| workspaceName | "user-test" |
| baseWorkspaceName | "live" |
| newContentStreamId | "user-cs-identifier" |
And the graph projection is fully up to date

################
# PUBLISHING
################
Scenario: It is possible to publish a single node; and only this one is live.
# create nodes in user WS
Given I am in content stream "user-cs-identifier" and dimension space point {}
And the following CreateNodeAggregateWithNode commands are executed:
| nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | tetheredDescendantNodeAggregateIds |
| sir-david-nodenborough | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | document | {} |
And I remember NodeAggregateId of node "sir-david-nodenborough"s child "child2" as "child2Id"
And the following CreateNodeAggregateWithNode commands are executed:
| nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | tetheredDescendantNodeAggregateIds |
| nody-mc-nodeface | Neos.ContentRepository.Testing:Content | $child2Id | nody | {} |
When the command PublishIndividualNodesFromWorkspace is executed with payload:
| Key | Value |
| workspaceName | "user-test" |
| nodesToPublish | [{"contentStreamId": "user-cs-identifier", "dimensionSpacePoint": {}, "nodeAggregateId": "sir-david-nodenborough"}] |
| contentStreamIdForRemainingPart | "user-cs-identifier-remaining" |
And the graph projection is fully up to date

And I am in content stream "cs-identifier"

Then I expect a node identified by cs-identifier;sir-david-nodenborough;{} to exist in the content graph

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* source code.
*/

use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
use Neos\ContentRepository\Core\NodeType\NodeTypeName;
use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;

Expand Down Expand Up @@ -95,7 +97,34 @@ public static function fromJsonString(string $jsonString): self

public function merge(self $other): self
{
return new self(array_merge($this->nodeAggregateIds, $other->getNodeAggregateIds()));
return new self(array_merge($this->nodeAggregateIds, $other->nodeAggregateIds));
}

public function completeForNodeOfType(NodeTypeName $nodeTypeName, NodeTypeManager $nodeTypeManager): self
{
return self::fromArray($this->createNodeAggregateIdsForNodeType($nodeTypeName, $nodeTypeManager))
->merge($this);
}

/**
* @return array<string,NodeAggregateId>
*/
private function createNodeAggregateIdsForNodeType(
NodeTypeName $nodeTypeName,
NodeTypeManager $nodeTypeManager,
?string $pathPrefix = null
): array {
$nodeAggregateIds = [];
foreach ($nodeTypeManager->getNodeType($nodeTypeName)->getAutoCreatedChildNodes() as $nodeName => $childNodeType) {
$path = $pathPrefix ? $pathPrefix . '/' . $nodeName : $nodeName;
$nodeAggregateIds[$path] = NodeAggregateId::create();
$nodeAggregateIds = array_merge(
$nodeAggregateIds,
$this->createNodeAggregateIdsForNodeType($childNodeType->name, $nodeTypeManager, $path)
);
}

return $nodeAggregateIds;
}

public function getNodeAggregateId(NodePath $nodePath): ?NodeAggregateId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,11 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties(
$contentRepository
);
}
$descendantNodeAggregateIds = self::populateNodeAggregateIds(
$nodeType,
$command->tetheredDescendantNodeAggregateIds
$descendantNodeAggregateIds = $command->tetheredDescendantNodeAggregateIds->completeForNodeOfType(
$command->nodeTypeName,
$this->nodeTypeManager
);

// Write the auto-created descendant node aggregate ids back to the command;
// so that when rebasing the command, it stays fully deterministic.
$command = $command->withTetheredDescendantNodeAggregateIds($descendantNodeAggregateIds);
Expand Down Expand Up @@ -340,29 +341,4 @@ private function createTetheredWithNode(
$precedingNodeAggregateId
);
}

protected static function populateNodeAggregateIds(
NodeType $nodeType,
?NodeAggregateIdsByNodePaths $nodeAggregateIds,
NodePath $childPath = null
): NodeAggregateIdsByNodePaths {
if ($nodeAggregateIds === null) {
$nodeAggregateIds = NodeAggregateIdsByNodePaths::createEmpty();
}
// TODO: handle Multiple levels of autocreated child nodes
foreach ($nodeType->getAutoCreatedChildNodes() as $rawChildName => $childNodeType) {
$childName = NodeName::fromString($rawChildName);
$childPath = $childPath
? $childPath->appendPathSegment($childName)
: NodePath::fromString($childName->value);
if (!$nodeAggregateIds->getNodeAggregateId($childPath)) {
$nodeAggregateIds = $nodeAggregateIds->add(
$childPath,
NodeAggregateId::create()
);
}
}

return $nodeAggregateIds;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
use Neos\ContentRepository\Core\EventStore\EventsToPublish;
use Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher;
use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName;
use Neos\ContentRepository\Core\Feature\NodeCreation\Dto\NodeAggregateIdsByNodePaths;
use Neos\ContentRepository\Core\Feature\NodeRemoval\Event\NodeAggregateWasRemoved;
use Neos\ContentRepository\Core\Feature\NodeTypeChange\Command\ChangeNodeAggregateType;
use Neos\ContentRepository\Core\Feature\NodeTypeChange\Event\NodeAggregateTypeWasChanged;
Expand All @@ -36,7 +35,6 @@
use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\ContentRepository\Core\SharedModel\Node\NodeName;
use Neos\ContentRepository\Core\SharedModel\User\UserId;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\EventStore\Model\EventStream\ExpectedVersion;

Expand Down Expand Up @@ -87,12 +85,6 @@ abstract protected function areNodeTypeConstraintsImposedByGrandparentValid(
NodeType $nodeType
): bool;

abstract protected static function populateNodeAggregateIds(
NodeType $nodeType,
NodeAggregateIdsByNodePaths $nodeAggregateIds,
NodePath $childPath = null
): NodeAggregateIdsByNodePaths;

abstract protected function createEventsForMissingTetheredNode(
NodeAggregate $parentNodeAggregate,
Node $parentNode,
Expand Down Expand Up @@ -156,9 +148,9 @@ private function handleChangeNodeAggregateType(
/**************
* Preparation - make the command fully deterministic in case of rebase
**************/
$descendantNodeAggregateIds = static::populateNodeAggregateIds(
$newNodeType,
$command->tetheredDescendantNodeAggregateIds
$descendantNodeAggregateIds = $command->tetheredDescendantNodeAggregateIds->completeForNodeOfType(
$newNodeType->name,
$this->nodeTypeManager
);
// Write the auto-created descendant node aggregate ids back to the command;
// so that when rebasing the command, it stays fully deterministic.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

/*
* This file is part of the Neos.ContentRepository.Core package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Neos\ContentRepository\Core\Tests\Unit\Feature\NodeCreation\Dto;

use Neos\ContentRepository\Core\Feature\NodeCreation\Dto\NodeAggregateIdsByNodePaths;
use Neos\ContentRepository\Core\NodeType\DefaultNodeLabelGeneratorFactory;
use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
use Neos\ContentRepository\Core\NodeType\NodeTypeName;
use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use PHPUnit\Framework\TestCase;

/**
* Test cases for {@see NodeAggregateIdsByNodePaths}
*/
class NodeAggregateIdsByNodePathsTest extends TestCase
{
/**
* @param array<string,NodeAggregateId|null> $expectedNodeAggregateIdsByPath, null if the actual value is not important
* @dataProvider subjectProvider
*/
public function testCompleteForNodeOfType(NodeAggregateIdsByNodePaths $subject, array $expectedNodeAggregateIdsByPath): void
{
$nodeTypeManager = new NodeTypeManager(
fn (): array => [
'Neos.ContentRepository.Testing:Content' => [],
'Neos.ContentRepository.Testing:LeafDocument' => [
'childNodes' => [
'grandchild1' => [
'type' => 'Neos.ContentRepository.Testing:Content'
],
'grandchild2' => [
'type' => 'Neos.ContentRepository.Testing:Content'
]
]
],
'Neos.ContentRepository.Testing:Document' => [
'childNodes' => [
'child1' => [
'type' => 'Neos.ContentRepository.Testing:LeafDocument'
],
'child2' => [
'type' => 'Neos.ContentRepository.Testing:LeafDocument'
]
]
]
],
new DefaultNodeLabelGeneratorFactory()
);

$completeSubject = $subject->completeForNodeOfType(
NodeTypeName::fromString('Neos.ContentRepository.Testing:Document'),
$nodeTypeManager
);

foreach ($expectedNodeAggregateIdsByPath as $path => $explicitExpectedNodeAggregateId) {
$actualNodeAggregateId = $completeSubject->getNodeAggregateId(NodePath::fromString($path));
self::assertInstanceOf(NodeAggregateId::class, $actualNodeAggregateId);
if ($explicitExpectedNodeAggregateId instanceof NodeAggregateId) {
self::assertTrue($actualNodeAggregateId->equals($explicitExpectedNodeAggregateId));
}
}
}

public static function subjectProvider(): iterable
{
yield 'emptySubject' => [
'subject' => NodeAggregateIdsByNodePaths::createEmpty(),
'expectedNodeAggregateIdsByPath' => [
'child1' => null,
'child2' => null,
'child1/grandchild1' => null,
'child1/grandchild2' => null,
'child2/grandchild1' => null,
'child2/grandchild2' => null,
]
];

yield 'alreadyCompleteSubject' => [
'subject' => NodeAggregateIdsByNodePaths::fromArray([
'child1' => NodeAggregateId::fromString('child-1'),
'child2' => NodeAggregateId::fromString('child-2'),
'child1/grandchild1' => NodeAggregateId::fromString('grandchild-1-1'),
'child1/grandchild2' => NodeAggregateId::fromString('grandchild-1-2'),
'child2/grandchild1' => NodeAggregateId::fromString('grandchild-2-1'),
'child2/grandchild2' => NodeAggregateId::fromString('grandchild-2-1'),
]),
'expectedNodeAggregateIdsByPath' => [
'child1' => NodeAggregateId::fromString('child-1'),
'child2' => NodeAggregateId::fromString('child-2'),
'child1/grandchild1' => NodeAggregateId::fromString('grandchild-1-1'),
'child1/grandchild2' => NodeAggregateId::fromString('grandchild-1-2'),
'child2/grandchild1' => NodeAggregateId::fromString('grandchild-2-1'),
'child2/grandchild2' => NodeAggregateId::fromString('grandchild-2-1'),
]
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate;
use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints;
use Neos\ContentRepository\Core\SharedModel\Node\NodeName;
use Neos\ContentRepository\Core\SharedModel\User\UserId;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
Expand Down Expand Up @@ -52,6 +53,11 @@ trait CRTestSuiteRuntimeVariables

protected ?NodeAggregate $currentNodeAggregate = null;

/**
* @var array<string,NodeAggregateId>
*/
protected array $rememberedNodeAggregateIds = [];

/**
* @Given /^I am in content repository "([^"]*)"$/
*/
Expand Down Expand Up @@ -150,6 +156,17 @@ public function getCurrentSubgraph(): ContentSubgraphInterface
);
}

/**
* @Given /^I remember NodeAggregateId of node "([^"]*)"s child "([^"]*)" as "([^"]*)"$/
*/
public function iRememberNodeAggregateIdOfNodesChildAs(string $parentNodeAggregateId, string $childNodeName, string $indexName): void
{
$this->rememberedNodeAggregateIds[$indexName] = $this->getCurrentSubgraph()->findChildNodeConnectedThroughEdgeName(
NodeAggregateId::fromString($parentNodeAggregateId),
NodeName::fromString($childNodeName)
)->nodeAggregateId;
}

protected function getCurrentNodeAggregateId(): NodeAggregateId
{
assert($this->currentNode instanceof Node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,15 @@ public function theFollowingCreateNodeAggregateWithNodeCommandsAreExecuted(Table
$originDimensionSpacePoint = isset($row['originDimensionSpacePoint'])
? OriginDimensionSpacePoint::fromJsonString($row['originDimensionSpacePoint'])
: OriginDimensionSpacePoint::fromDimensionSpacePoint($this->currentDimensionSpacePoint);
$rawParentNodeAggregateId = $row['parentNodeAggregateId'];
$command = CreateNodeAggregateWithNode::create(
$contentStreamId,
NodeAggregateId::fromString($row['nodeAggregateId']),
NodeTypeName::fromString($row['nodeTypeName']),
$originDimensionSpacePoint,
NodeAggregateId::fromString($row['parentNodeAggregateId']),
\str_starts_with($rawParentNodeAggregateId, '$')
? $this->rememberedNodeAggregateIds[\mb_substr($rawParentNodeAggregateId, 1)]
: NodeAggregateId::fromString($rawParentNodeAggregateId),
isset($row['succeedingSiblingNodeAggregateId'])
? NodeAggregateId::fromString($row['succeedingSiblingNodeAggregateId'])
: null,
Expand Down

0 comments on commit 041670b

Please sign in to comment.