From a31efab115ed23d5af7f5f683508619dc6a258a2 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Thu, 19 Sep 2024 16:18:05 +0200 Subject: [PATCH 1/4] FEATURE: Add UpdateRootNodeAggregateDimensions as transformation --- .../UpdateRootNodeAggregateDimensions.feature | 144 ++++++++++++++++++ .../src/NodeMigrationServiceFactory.php | 2 + ...gregateDimensionsTransformationFactory.php | 68 +++++++++ .../References/NodeMigrations.rst | 12 ++ 4 files changed, 226 insertions(+) create mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/UpdateRootNodeAggregateDimensions.feature create mode 100644 Neos.ContentRepository.NodeMigration/src/Transformation/UpdateRootNodeAggregateDimensionsTransformationFactory.php diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/UpdateRootNodeAggregateDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/UpdateRootNodeAggregateDimensions.feature new file mode 100644 index 00000000000..099036afb35 --- /dev/null +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/UpdateRootNodeAggregateDimensions.feature @@ -0,0 +1,144 @@ +@contentrepository @adapters=DoctrineDBAL +Feature: Update root node aggregate dimensions + + Creates empty root node aggregate dimensions for each allowed dimension combination and removes them for all non-configured ones. + + Background: + ######################## + # SETUP + ######################## + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | language | mul, de, en, ch | ch->de->mul, en->mul | + And using the following node types: + """yaml + 'Neos.ContentRepository:Root': + constraints: + nodeTypes: + 'Neos.ContentRepository.Testing:Document': true + 'Neos.ContentRepository.Testing:OtherDocument': true + + 'Neos.ContentRepository.Testing:Document': [] + 'Neos.ContentRepository.Testing:OtherDocument': [] + """ + 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 the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + + Scenario: Run migration after adding a new dimension value + # we change the dimension configuration + Given I change the content dimensions in content repository "default" to: + | Identifier | Values | Generalizations | + | language | mul, de, en, ch, fr | ch->de->mul, en->mul | + + When I run the following node migration for workspace "live", creating target workspace "migration-workspace" on contentStreamId "migration-cs", without publishing on success: + """yaml + migration: + - + transformations: + - + type: 'UpdateRootNodeAggregateDimensions' + settings: + nodeType: 'Neos.ContentRepository:Root' + """ + + When I am in workspace "live" + Then I expect the node aggregate "lady-eleonode-rootford" to exist + And I expect this node aggregate to occupy dimension space points [{}] + And I expect this node aggregate to cover dimension space points [{"language":"mul"},{"language":"de"},{"language":"en"},{"language":"ch"}] + + When I am in workspace "migration-workspace" + Then I expect the node aggregate "lady-eleonode-rootford" to exist + And I expect this node aggregate to occupy dimension space points [{}] + And I expect this node aggregate to cover dimension space points [{"language":"mul"},{"language":"de"},{"language":"en"},{"language":"ch"},{"language":"fr"}] + + When I run integrity violation detection + Then I expect the integrity violation detection result to contain exactly 0 errors + + Scenario: Run migration after removing a new dimension value + # we change the dimension configuration + Given I change the content dimensions in content repository "default" to: + | Identifier | Values | Generalizations | + | language | mul, de, ch | ch->de->mul | + + When I run the following node migration for workspace "live", creating target workspace "migration-workspace" on contentStreamId "migration-cs", without publishing on success: + """yaml + migration: + - + transformations: + - + type: 'UpdateRootNodeAggregateDimensions' + settings: + nodeType: 'Neos.ContentRepository:Root' + """ + + When I am in workspace "live" + Then I expect the node aggregate "lady-eleonode-rootford" to exist + And I expect this node aggregate to occupy dimension space points [{}] + And I expect this node aggregate to cover dimension space points [{"language":"mul"},{"language":"de"},{"language":"en"},{"language":"ch"}] + + When I am in workspace "migration-workspace" + Then I expect the node aggregate "lady-eleonode-rootford" to exist + And I expect this node aggregate to occupy dimension space points [{}] + And I expect this node aggregate to cover dimension space points [{"language":"mul"},{"language":"de"},{"language":"ch"}] + + When I run integrity violation detection + Then I expect the integrity violation detection result to contain exactly 0 errors + + Scenario: Run migration after renaming a new dimension value + # we change the dimension configuration + Given I change the content dimensions in content repository "default" to: + | Identifier | Values | Generalizations | + | language | mul, de_DE, en, ch | ch->de_DE->mul, en->mul | + + When I run the following node migration for workspace "live", creating target workspace "migration-workspace" on contentStreamId "migration-cs", without publishing on success: + """yaml + migration: + - + transformations: + - + type: 'UpdateRootNodeAggregateDimensions' + settings: + nodeType: 'Neos.ContentRepository:Root' + """ + + When I am in workspace "live" + Then I expect the node aggregate "lady-eleonode-rootford" to exist + And I expect this node aggregate to occupy dimension space points [{}] + And I expect this node aggregate to cover dimension space points [{"language":"mul"},{"language":"de"},{"language":"en"},{"language":"ch"}] + + When I am in workspace "migration-workspace" + Then I expect the node aggregate "lady-eleonode-rootford" to exist + And I expect this node aggregate to occupy dimension space points [{}] + And I expect this node aggregate to cover dimension space points [{"language":"mul"},{"language":"de_DE"},{"language":"en"},{"language":"ch"}] + + When I run integrity violation detection + Then I expect the integrity violation detection result to contain exactly 0 errors + + Scenario: Without migration, creating new nodeaggregates in new dimensionspacepoint will fail + # we change the dimension configuration + Given I change the content dimensions in content repository "default" to: + | Identifier | Values | Generalizations | + | language | mul, de, en, ch, fr | ch->de->mul, en->mul | + + When I am in workspace "live" and dimension space point {"language": "fr"} + And the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | nodeTypeName | "Neos.ContentRepository.Testing:Document" | + | originDimensionSpacePoint | {"language": "fr"} | + | parentNodeAggregateId | "lady-eleonode-rootford" | + Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint" + + When I run integrity violation detection + Then I expect the integrity violation detection result to contain exactly 0 errors diff --git a/Neos.ContentRepository.NodeMigration/src/NodeMigrationServiceFactory.php b/Neos.ContentRepository.NodeMigration/src/NodeMigrationServiceFactory.php index d78d5a54316..f6c3f5fee33 100644 --- a/Neos.ContentRepository.NodeMigration/src/NodeMigrationServiceFactory.php +++ b/Neos.ContentRepository.NodeMigration/src/NodeMigrationServiceFactory.php @@ -23,6 +23,7 @@ use Neos\ContentRepository\NodeMigration\Transformation\RenamePropertyTransformationFactory; use Neos\ContentRepository\NodeMigration\Transformation\StripTagsOnPropertyTransformationFactory; use Neos\ContentRepository\NodeMigration\Transformation\TransformationsFactory; +use Neos\ContentRepository\NodeMigration\Transformation\UpdateRootNodeAggregateDimensionsTransformationFactory; /** * @implements ContentRepositoryServiceFactoryInterface @@ -49,6 +50,7 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor $transformationsFactory->registerTransformation('RenameNodeAggregate', new RenameNodeAggregateTransformationFactory()); $transformationsFactory->registerTransformation('RenameProperty', new RenamePropertyTransformationFactory()); $transformationsFactory->registerTransformation('StripTagsOnProperty', new StripTagsOnPropertyTransformationFactory()); + $transformationsFactory->registerTransformation('UpdateRootNodeAggregateDimensions', new UpdateRootNodeAggregateDimensionsTransformationFactory()); return new NodeMigrationService( $serviceFactoryDependencies->contentRepository, diff --git a/Neos.ContentRepository.NodeMigration/src/Transformation/UpdateRootNodeAggregateDimensionsTransformationFactory.php b/Neos.ContentRepository.NodeMigration/src/Transformation/UpdateRootNodeAggregateDimensionsTransformationFactory.php new file mode 100644 index 00000000000..f10194ce8b7 --- /dev/null +++ b/Neos.ContentRepository.NodeMigration/src/Transformation/UpdateRootNodeAggregateDimensionsTransformationFactory.php @@ -0,0 +1,68 @@ +> $settings + */ + public function build( + array $settings, + ContentRepository $contentRepository + ): GlobalTransformationInterface|NodeAggregateBasedTransformationInterface|NodeBasedTransformationInterface { + if (!isset($settings['nodeType'])) { + throw new MigrationException( + 'The "nodeType" must not be empty.', + 1726754800 + ); + + } + try { + $nodeTypeName = NodeTypeName::fromString($settings['nodeType']); + } catch (\InvalidArgumentException $exception) { + throw new MigrationException( + sprintf('The given "nodeType" ("%s") is not valid.', $settings['nodeType']), + 1726754273 + ); + } + return new class ( + $nodeTypeName, + $contentRepository + ) implements GlobalTransformationInterface { + public function __construct( + private readonly NodeTypeName $nodeTypeName, + private readonly ContentRepository $contentRepository, + ) { + } + + public function execute( + WorkspaceName $workspaceNameForWriting, + ): CommandResult { + + $rootNodeAggregate = $this->contentRepository->getContentGraph($workspaceNameForWriting)->findRootNodeAggregateByType($this->nodeTypeName); + + if (!$rootNodeAggregate) { + throw new MigrationException( + sprintf('There is no root node with the given "nodeType" ("%s") in the content repository.', $this->nodeTypeName->value), + 1726754019 + ); + } + + return $this->contentRepository->handle( + UpdateRootNodeAggregateDimensions::create( + $workspaceNameForWriting, + $rootNodeAggregate->nodeAggregateId + ) + ); + } + }; + } +} diff --git a/Neos.Neos/Documentation/References/NodeMigrations.rst b/Neos.Neos/Documentation/References/NodeMigrations.rst index 7ffc28d4343..a4cc4e3b0e4 100644 --- a/Neos.Neos/Documentation/References/NodeMigrations.rst +++ b/Neos.Neos/Documentation/References/NodeMigrations.rst @@ -21,6 +21,7 @@ The Content Repository comes with a number of common transformations: - ``RenameNodeAggregate`` - ``RenameProperty`` - ``StripTagsOnProperty`` +- ``UpdateRootNodeAggregateDimensions`` They all implement the ``Neos\ContentRepository\NodeMigration\Transformation\TransformationFactoryInterface``. Custom transformations can be developed against that interface as well, just use the fully qualified class name for those when specifying @@ -262,6 +263,17 @@ Options Reference: ``property`` (string) The name of the property to work on. +UpdateRootNodeAggregateDimensions +~~~~~~~~~~~~~ + +Updates all root node aggregate dimensions regarding the current content repository configuration. + +Creates empty root node aggregate dimensions for each allowed dimension combination and removes them for all non-configured ones. + +Options Reference: + +``nodeType`` (string) + The node type name of the root node. Filters Reference From 38d0db03049a3c9fd301b1450854beb8e31b23d7 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Thu, 19 Sep 2024 16:21:56 +0200 Subject: [PATCH 2/4] FEATURE: Add UpdateRootNodeAggregateDimensions as transformation --- Neos.Neos/Documentation/References/NodeMigrations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.Neos/Documentation/References/NodeMigrations.rst b/Neos.Neos/Documentation/References/NodeMigrations.rst index a4cc4e3b0e4..a4892669302 100644 --- a/Neos.Neos/Documentation/References/NodeMigrations.rst +++ b/Neos.Neos/Documentation/References/NodeMigrations.rst @@ -273,7 +273,7 @@ Creates empty root node aggregate dimensions for each allowed dimension combinat Options Reference: ``nodeType`` (string) - The node type name of the root node. + The node type name of the root node. For Neos this is usually "Neos.Neos:Sites" Filters Reference From a56ba5070466568bfa75a552e7f1641fa83c4ea6 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Thu, 19 Sep 2024 16:27:08 +0200 Subject: [PATCH 3/4] FEATURE: Add UpdateRootNodeAggregateDimensions as transformation --- ...UpdateRootNodeAggregateDimensionsTransformationFactory.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Neos.ContentRepository.NodeMigration/src/Transformation/UpdateRootNodeAggregateDimensionsTransformationFactory.php b/Neos.ContentRepository.NodeMigration/src/Transformation/UpdateRootNodeAggregateDimensionsTransformationFactory.php index f10194ce8b7..bf7a0e0f415 100644 --- a/Neos.ContentRepository.NodeMigration/src/Transformation/UpdateRootNodeAggregateDimensionsTransformationFactory.php +++ b/Neos.ContentRepository.NodeMigration/src/Transformation/UpdateRootNodeAggregateDimensionsTransformationFactory.php @@ -1,5 +1,7 @@ > $settings + * @param array $settings */ public function build( array $settings, From 4a31b0ac1c27321e86bbd15ccf18e9cf3aafb2d8 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Thu, 19 Sep 2024 16:43:27 +0200 Subject: [PATCH 4/4] FEATURE: Add UpdateRootNodeAggregateDimensions as transformation --- .../UpdateRootNodeAggregateDimensions.feature | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/UpdateRootNodeAggregateDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/UpdateRootNodeAggregateDimensions.feature index 099036afb35..e1a5cb78ed3 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/UpdateRootNodeAggregateDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/UpdateRootNodeAggregateDimensions.feature @@ -57,11 +57,13 @@ Feature: Update root node aggregate dimensions And I expect this node aggregate to occupy dimension space points [{}] And I expect this node aggregate to cover dimension space points [{"language":"mul"},{"language":"de"},{"language":"en"},{"language":"ch"}] - When I am in workspace "migration-workspace" + When I am in workspace "migration-workspace" and dimension space point {"language": "fr"} Then I expect the node aggregate "lady-eleonode-rootford" to exist And I expect this node aggregate to occupy dimension space points [{}] And I expect this node aggregate to cover dimension space points [{"language":"mul"},{"language":"de"},{"language":"en"},{"language":"ch"},{"language":"fr"}] + Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node migration-cs;lady-eleonode-rootford;{} + When I run integrity violation detection Then I expect the integrity violation detection result to contain exactly 0 errors @@ -87,11 +89,13 @@ Feature: Update root node aggregate dimensions And I expect this node aggregate to occupy dimension space points [{}] And I expect this node aggregate to cover dimension space points [{"language":"mul"},{"language":"de"},{"language":"en"},{"language":"ch"}] - When I am in workspace "migration-workspace" + When I am in workspace "migration-workspace" and dimension space point {"language": "en"} Then I expect the node aggregate "lady-eleonode-rootford" to exist And I expect this node aggregate to occupy dimension space points [{}] And I expect this node aggregate to cover dimension space points [{"language":"mul"},{"language":"de"},{"language":"ch"}] + Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to no node + When I run integrity violation detection Then I expect the integrity violation detection result to contain exactly 0 errors @@ -117,11 +121,13 @@ Feature: Update root node aggregate dimensions And I expect this node aggregate to occupy dimension space points [{}] And I expect this node aggregate to cover dimension space points [{"language":"mul"},{"language":"de"},{"language":"en"},{"language":"ch"}] - When I am in workspace "migration-workspace" + When I am in workspace "migration-workspace" and dimension space point {"language": "de_DE"} Then I expect the node aggregate "lady-eleonode-rootford" to exist And I expect this node aggregate to occupy dimension space points [{}] And I expect this node aggregate to cover dimension space points [{"language":"mul"},{"language":"de_DE"},{"language":"en"},{"language":"ch"}] + Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node migration-cs;lady-eleonode-rootford;{} + When I run integrity violation detection Then I expect the integrity violation detection result to contain exactly 0 errors @@ -140,5 +146,7 @@ Feature: Update root node aggregate dimensions | parentNodeAggregateId | "lady-eleonode-rootford" | Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint" + Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to no node + When I run integrity violation detection Then I expect the integrity violation detection result to contain exactly 0 errors