From 92890afb92cc89a2f0c30e704606848134d03041 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Tue, 24 Jun 2025 13:43:13 +0200 Subject: [PATCH] TASK: Add node state information isCreated, isModified and isRemoved to TreeNode The information is obtained from the pendingChangeProjection nodes that are removed but not yet published in the current workspace will still show up but not their children that only inherited the removed tag and have no change pending. --- Classes/Application/Shared/TreeNode.php | 6 + .../Application/Shared/TreeNodeBuilder.php | 6 + .../Infrastructure/ESCR/NodeChangeState.php | 50 +++ .../ESCR/NodeChangeStateCollection.php | 58 +++ Classes/Infrastructure/ESCR/NodeService.php | 48 +- .../ESCR/NodeServiceFactory.php | 17 +- .../Api/01-GetChildrenForTreeNode.feature | 50 ++- .../Features/Api/03-GetTreeQuery.feature | 201 +++++++-- .../04-GetTreeQuery-WorkspaceChanges.feature | 420 ++++++++++++++++++ 9 files changed, 795 insertions(+), 61 deletions(-) create mode 100644 Classes/Infrastructure/ESCR/NodeChangeState.php create mode 100644 Classes/Infrastructure/ESCR/NodeChangeStateCollection.php create mode 100644 Tests/Behavior/Features/Api/04-GetTreeQuery-WorkspaceChanges.feature diff --git a/Classes/Application/Shared/TreeNode.php b/Classes/Application/Shared/TreeNode.php index 616b979bc4..d6edef138a 100644 --- a/Classes/Application/Shared/TreeNode.php +++ b/Classes/Application/Shared/TreeNode.php @@ -31,6 +31,9 @@ public function __construct( public readonly bool $isMatchedByFilter, public readonly bool $isDisabled, public readonly bool $isHiddenInMenu, + public readonly bool $isCreated, + public readonly bool $isModified, + public readonly bool $isRemoved, // todo rename to hasTimeableNodeVisibility? public readonly bool $hasScheduledDisabledState, public readonly bool $hasUnloadedChildren, @@ -48,6 +51,9 @@ public function jsonSerialize(): mixed 'isMatchedByFilter' => $this->isMatchedByFilter, 'isDisabled' => $this->isDisabled, 'isHiddenInMenu' => $this->isHiddenInMenu, + 'isCreated' => $this->isCreated, + 'isModified' => $this->isModified, + 'isRemoved' => $this->isRemoved, 'hasScheduledDisabledState' => $this->hasScheduledDisabledState, 'hasUnloadedChildren' => $this->hasUnloadedChildren, 'children' => $this->children, diff --git a/Classes/Application/Shared/TreeNodeBuilder.php b/Classes/Application/Shared/TreeNodeBuilder.php index 3e7386a087..99d0ee4e02 100644 --- a/Classes/Application/Shared/TreeNodeBuilder.php +++ b/Classes/Application/Shared/TreeNodeBuilder.php @@ -38,6 +38,9 @@ public function __construct( private bool $isMatchedByFilter, private readonly bool $isDisabled, private readonly bool $isHiddenInMenu, + private bool $isCreated, + private bool $isModified, + private bool $isRemoved, private readonly bool $hasScheduledDisabledState, private bool $hasUnloadedChildren, ) { @@ -89,6 +92,9 @@ public function build(): TreeNode isMatchedByFilter: $this->isMatchedByFilter, isDisabled: $this->isDisabled, isHiddenInMenu: $this->isHiddenInMenu, + isCreated: $this->isCreated, + isModified: $this->isModified, + isRemoved: $this->isRemoved, hasScheduledDisabledState: $this->hasScheduledDisabledState, hasUnloadedChildren: $this->hasUnloadedChildren, children: $this->buildChildren(), diff --git a/Classes/Infrastructure/ESCR/NodeChangeState.php b/Classes/Infrastructure/ESCR/NodeChangeState.php new file mode 100644 index 0000000000..b22e6a00a5 --- /dev/null +++ b/Classes/Infrastructure/ESCR/NodeChangeState.php @@ -0,0 +1,50 @@ +created, + $change->changed || $change->deleted, + $change->deleted, + ); + } + + public function withAppliedAdditionalChange(Change $change): self + { + return new self( + $change->created || $this->isCreated, + ($change->changed || $change->moved) || $this->isChanged, + $change->deleted || $this->isDeleted, + ); + } +} diff --git a/Classes/Infrastructure/ESCR/NodeChangeStateCollection.php b/Classes/Infrastructure/ESCR/NodeChangeStateCollection.php new file mode 100644 index 0000000000..aed3ead0c5 --- /dev/null +++ b/Classes/Infrastructure/ESCR/NodeChangeStateCollection.php @@ -0,0 +1,58 @@ + $changesByNodeAggregateId + */ + private function __construct( + private array $changesByNodeAggregateId, + ) { + } + + public static function create(Changes $changes, DimensionSpacePoint $dimensionSpacePoint): self + { + /** + * @var array $changesByNodeAggregateId + */ + $changesByNodeAggregateId = []; + + foreach ($changes as $change) { + if ($change->originDimensionSpacePoint === null || $change->originDimensionSpacePoint->equals($dimensionSpacePoint)) { + if ($pendingChange = $changesByNodeAggregateId[$change->nodeAggregateId->value] ?? null) { + $changesByNodeAggregateId[$change->nodeAggregateId->value] = $pendingChange->withAppliedAdditionalChange($change); + } else { + $changesByNodeAggregateId[$change->nodeAggregateId->value] = NodeChangeState::fromChange($change); + } + } + } + return new self( + $changesByNodeAggregateId + ); + } + + public function findByNodeAggreqateId(NodeAggregateId $nodeAggregateId): ?NodeChangeState + { + return $this->changesByNodeAggregateId[$nodeAggregateId->value] ?? null; + } +} diff --git a/Classes/Infrastructure/ESCR/NodeService.php b/Classes/Infrastructure/ESCR/NodeService.php index 224ca8da49..f790a01532 100644 --- a/Classes/Infrastructure/ESCR/NodeService.php +++ b/Classes/Infrastructure/ESCR/NodeService.php @@ -33,9 +33,11 @@ use Neos\Flow\Annotations as Flow; use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; use Neos\Neos\Domain\SubtreeTagging\NeosSubtreeTag; +use Neos\Neos\PendingChangesProjection\Changes; use Neos\Neos\Ui\Application\Shared\NodeTypeWasNotFound; use Neos\Neos\Ui\Application\Shared\NodeWasNotFound; use Neos\Neos\Ui\Application\Shared\TreeNodeBuilder; +use Neos\Workspace\Ui\ViewModel\PendingChanges; /** * @internal @@ -46,7 +48,8 @@ final class NodeService public function __construct( private readonly ContentRepository $contentRepository, public readonly ContentSubgraphInterface $subgraph, - private readonly NodeLabelGeneratorInterface $nodeLabelGenerator + private readonly NodeLabelGeneratorInterface $nodeLabelGenerator, + public readonly NodeChangeStateCollection $pendingChanges, ) { } @@ -88,26 +91,30 @@ public function getLabelForNode(Node $node): string public function findParentNode(Node $node): ?Node { - return $this->subgraph->findParentNode($node->aggregateId); + $parent = $this->subgraph->findParentNode($node->aggregateId); + return $parent && $this->filterRemovedNodes($parent) ? $parent : null; } public function findPrecedingSiblingNodes(Node $node): Nodes { $filter = FindPrecedingSiblingNodesFilter::create(); - return $this->subgraph->findPrecedingSiblingNodes($node->aggregateId, $filter); + return $this->subgraph->findPrecedingSiblingNodes($node->aggregateId, $filter) + ->filter(fn (Node $node) => $this->filterRemovedNodes($node)); } public function findSucceedingSiblingNodes(Node $node): Nodes { $filter = FindSucceedingSiblingNodesFilter::create(); - return $this->subgraph->findSucceedingSiblingNodes($node->aggregateId, $filter); + return $this->subgraph->findSucceedingSiblingNodes($node->aggregateId, $filter) + ->filter(fn (Node $node) => $this->filterRemovedNodes($node)); } public function findNodeByAbsoluteNodePath(AbsoluteNodePath $path): ?Node { - return $this->subgraph->findNodeByAbsolutePath($path); + $node = $this->subgraph->findNodeByAbsolutePath($path); + return $node && $this->filterRemovedNodes($node) ? $node : null; } public function search(Node $rootNode, string $searchTerm, NodeTypeFilter $nodeTypeFilter): Nodes @@ -117,7 +124,8 @@ public function search(Node $rootNode, string $searchTerm, NodeTypeFilter $nodeT searchTerm: $searchTerm ); - return $this->subgraph->findDescendantNodes($rootNode->aggregateId, $filter); + return $this->subgraph->findDescendantNodes($rootNode->aggregateId, $filter) + ->filter(fn (Node $node) => $this->filterRemovedNodes($node)); } public function createTreeBuilderForRootNode( @@ -135,6 +143,7 @@ public function createTreeBuilderForRootNode( public function createTreeNodeBuilderForNode(Node $node): TreeNodeBuilder { $nodeType = $this->requireNodeTypeByName($node->nodeTypeName); + $pendingChange = $this->pendingChanges->findByNodeAggreqateId($node->aggregateId); return new TreeNodeBuilder( nodeAddress: NodeAddress::fromNode($node), @@ -147,7 +156,10 @@ public function createTreeNodeBuilderForNode(Node $node): TreeNodeBuilder hasScheduledDisabledState: $node->getProperty('enableAfterDateTime') instanceof \DateTimeInterface || $node->getProperty('disableAfterDateTime') instanceof \DateTimeInterface, - hasUnloadedChildren: false + hasUnloadedChildren: false, + isCreated: $pendingChange ? $pendingChange->isCreated : false, + isModified: $pendingChange ? $pendingChange->isChanged : false, + isRemoved: $pendingChange ? $pendingChange->isDeleted : false, ); } @@ -157,7 +169,8 @@ public function findChildNodes(Node $parentNode, NodeTypeCriteria $nodeTypeCrite nodeTypes: $nodeTypeCriteria, ); - return $this->subgraph->findChildNodes($parentNode->aggregateId, $filter); + return $this->subgraph->findChildNodes($parentNode->aggregateId, $filter) + ->filter(fn (Node $node) => $this->filterRemovedNodes($node)); } public function getNumberOfChildNodes(Node $parentNode, NodeTypeCriteria $nodeTypeCriteria): int @@ -175,4 +188,23 @@ public function findAncestorNodes(Node $node): Nodes return $this->subgraph->findAncestorNodes($node->aggregateId, $filter); } + + /** + * Filter function to remove all nodes that are tagged as removed + * AND do not have the tag assigned directly (deleted as children) + * AND are not in the current changes (other workspaces) + */ + public function filterRemovedNodes(Node $node): bool + { + if (!$node->tags->contain(NeosSubtreeTag::removed())) { + return true; + } + if ($node->tags->withoutInherited()->contain(NeosSubtreeTag::removed())) { + $changeState = $this->pendingChanges->findByNodeAggreqateId($node->aggregateId); + if ($changeState instanceof NodeChangeState && $changeState->isDeleted) { + return true; + } + } + return false; + } } diff --git a/Classes/Infrastructure/ESCR/NodeServiceFactory.php b/Classes/Infrastructure/ESCR/NodeServiceFactory.php index 2fcbdd872a..fedf094ba3 100644 --- a/Classes/Infrastructure/ESCR/NodeServiceFactory.php +++ b/Classes/Infrastructure/ESCR/NodeServiceFactory.php @@ -15,12 +15,15 @@ namespace Neos\Neos\Ui\Infrastructure\ESCR; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; +use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; use Neos\Neos\Domain\SubtreeTagging\NeosVisibilityConstraints; +use Neos\Neos\PendingChangesProjection\ChangeFinder; +use Neos\Neos\PendingChangesProjection\ChangeProjection; /** * @internal @@ -41,17 +44,25 @@ public function create( ): NodeService { $contentRepository = $this->contentRepositoryRegistry ->get($contentRepositoryId); - $subgraph = $contentRepository - ->getContentGraph($workspaceName) + $contentGraph = $contentRepository->getContentGraph($workspaceName); + $subgraph = $contentGraph ->getSubgraph( $dimensionSpacePoint, - NeosVisibilityConstraints::excludeRemoved() + VisibilityConstraints::createEmpty() ); + $changeFinder = $contentRepository->projectionState(ChangeFinder::class); + $changes = $changeFinder->findByContentStreamId($contentGraph->getContentStreamId()); + $pendingChanges = NodeChangeStateCollection::create( + $changes, + $dimensionSpacePoint, + ); + return new NodeService( contentRepository: $contentRepository, subgraph: $subgraph, nodeLabelGenerator: $this->nodeLabelGenerator, + pendingChanges: $pendingChanges, ); } } diff --git a/Tests/Behavior/Features/Api/01-GetChildrenForTreeNode.feature b/Tests/Behavior/Features/Api/01-GetChildrenForTreeNode.feature index 82105d929b..63abbab0ac 100644 --- a/Tests/Behavior/Features/Api/01-GetChildrenForTreeNode.feature +++ b/Tests/Behavior/Features/Api/01-GetChildrenForTreeNode.feature @@ -148,7 +148,10 @@ Feature: GetChildrenForTreeNode "isMatchedByFilter": true, "label": "My Node: features", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"features\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "children": [], @@ -160,7 +163,10 @@ Feature: GetChildrenForTreeNode "isMatchedByFilter": true, "label": "My Node: linkable", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"linkable\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } ] } @@ -209,7 +215,10 @@ Feature: GetChildrenForTreeNode "isMatchedByFilter": true, "label": "My Node: a", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-a-multi-dsp\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "children": [], @@ -221,7 +230,10 @@ Feature: GetChildrenForTreeNode "isMatchedByFilter": true, "label": "My Node: b", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-b-disabled\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "children": [], @@ -233,7 +245,10 @@ Feature: GetChildrenForTreeNode "isMatchedByFilter": true, "label": "My Other Node", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-c-other-type\"}", - "nodeTypeLabel": "My Other Document Type" + "nodeTypeLabel": "My Other Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } ] } @@ -264,7 +279,10 @@ Feature: GetChildrenForTreeNode "isMatchedByFilter": true, "label": "My Other Node", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-c-other-type\"}", - "nodeTypeLabel": "My Other Document Type" + "nodeTypeLabel": "My Other Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } ] } @@ -295,7 +313,10 @@ Feature: GetChildrenForTreeNode "isMatchedByFilter": true, "label": "My Node: a (de)", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"de\"},\"aggregateId\":\"feature-a-multi-dsp\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } ] } @@ -326,7 +347,10 @@ Feature: GetChildrenForTreeNode "isMatchedByFilter": true, "label": "My Node: a", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"linkable-a-default\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "children": [], @@ -338,7 +362,10 @@ Feature: GetChildrenForTreeNode "isMatchedByFilter": true, "label": "My Node: b", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"linkable-b-included\"}", - "nodeTypeLabel": "My Included Document Type" + "nodeTypeLabel": "My Included Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "children": [], @@ -350,7 +377,10 @@ Feature: GetChildrenForTreeNode "isMatchedByFilter": true, "label": "My Node: c", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"linkable-c-excluded\"}", - "nodeTypeLabel": "My Excluded Document Type" + "nodeTypeLabel": "My Excluded Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } ] } diff --git a/Tests/Behavior/Features/Api/03-GetTreeQuery.feature b/Tests/Behavior/Features/Api/03-GetTreeQuery.feature index 4c7834a440..142e9fa08c 100644 --- a/Tests/Behavior/Features/Api/03-GetTreeQuery.feature +++ b/Tests/Behavior/Features/Api/03-GetTreeQuery.feature @@ -143,7 +143,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "Homepage site-a", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"homepage\"}", - "nodeTypeLabel": "Home Page Type" + "nodeTypeLabel": "Home Page Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } } } @@ -175,7 +178,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "Homepage site-a", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"homepage\"}", - "nodeTypeLabel": "Home Page Type" + "nodeTypeLabel": "Home Page Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } ], "hasScheduledDisabledState": false, @@ -186,7 +192,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "Neos.Neos:Sites", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"sites\"}", - "nodeTypeLabel": "" + "nodeTypeLabel": "", + "isCreated": false, + "isModified": false, + "isRemoved": false } } } @@ -219,7 +228,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Node: features", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"features\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "children": [], @@ -231,7 +243,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Node: search", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"search\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } ], "hasScheduledDisabledState": false, @@ -242,7 +257,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "Homepage site-a", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"homepage\"}", - "nodeTypeLabel": "Home Page Type" + "nodeTypeLabel": "Home Page Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } } } @@ -272,7 +290,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Node: a1", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-a1-default\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } } } @@ -305,7 +326,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Node: a1", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-a1-default\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false },{ "children": [], "hasScheduledDisabledState": false, @@ -316,7 +340,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Node: a2", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-a2-default\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false }], "hasScheduledDisabledState": false, "hasUnloadedChildren": false, @@ -326,7 +353,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Node: a", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-a-default\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "children": [], @@ -338,7 +368,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Node: b", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-b-disabled\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "children": [{ @@ -351,7 +384,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Node: c1", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-c1-default\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false }], "hasScheduledDisabledState": false, "hasUnloadedChildren": false, @@ -361,7 +397,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Other Node", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-c-other-type\"}", - "nodeTypeLabel": "My Other Document Type" + "nodeTypeLabel": "My Other Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "children": [], @@ -373,7 +412,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Node: d", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-d-multi-dsp\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } ], "hasScheduledDisabledState": false, @@ -384,7 +426,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Node: features", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"features\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } } } @@ -416,7 +461,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Other Node", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-c-other-type\"}", - "nodeTypeLabel": "My Other Document Type" + "nodeTypeLabel": "My Other Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } ], "hasScheduledDisabledState": false, @@ -427,7 +475,10 @@ Feature: GetTreeQuery "isMatchedByFilter": false, "label": "My Node: features", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"features\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } } } @@ -479,11 +530,20 @@ Feature: GetTreeQuery "isHiddenInMenu": false, "hasScheduledDisabledState": false, "hasUnloadedChildren": false, - "children": [] + "children": [], + "isCreated": false, + "isModified": false, + "isRemoved": false } - ] + ], + "isCreated": false, + "isModified": false, + "isRemoved": false } - ] + ], + "isCreated": false, + "isModified": false, + "isRemoved": false } } } @@ -524,7 +584,10 @@ Feature: GetTreeQuery "isHiddenInMenu": false, "hasScheduledDisabledState": false, "hasUnloadedChildren": false, - "children": [] + "children": [], + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"search-a-default\"}", @@ -547,7 +610,10 @@ Feature: GetTreeQuery "isHiddenInMenu": false, "hasScheduledDisabledState": false, "hasUnloadedChildren": false, - "children": [] + "children": [], + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"search-a3-other-text\"}", @@ -559,11 +625,20 @@ Feature: GetTreeQuery "isHiddenInMenu": false, "hasScheduledDisabledState": false, "hasUnloadedChildren": false, - "children": [] + "children": [], + "isCreated": false, + "isModified": false, + "isRemoved": false } - ] + ], + "isCreated": false, + "isModified": false, + "isRemoved": false } - ] + ], + "isCreated": false, + "isModified": false, + "isRemoved": false } } } @@ -604,7 +679,10 @@ Feature: GetTreeQuery "isHiddenInMenu": false, "hasScheduledDisabledState": false, "hasUnloadedChildren": false, - "children": [] + "children": [], + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"search-a-default\"}", @@ -627,11 +705,20 @@ Feature: GetTreeQuery "isHiddenInMenu": false, "hasScheduledDisabledState": false, "hasUnloadedChildren": false, - "children": [] + "children": [], + "isCreated": false, + "isModified": false, + "isRemoved": false } - ] + ], + "isCreated": false, + "isModified": false, + "isRemoved": false } - ] + ], + "isCreated": false, + "isModified": false, + "isRemoved": false } } } @@ -683,11 +770,20 @@ Feature: GetTreeQuery "isHiddenInMenu": false, "hasScheduledDisabledState": false, "hasUnloadedChildren": false, - "children": [] + "children": [], + "isCreated": false, + "isModified": false, + "isRemoved": false } - ] + ], + "isCreated": false, + "isModified": false, + "isRemoved": false } - ] + ], + "isCreated": false, + "isModified": false, + "isRemoved": false } } } @@ -725,7 +821,10 @@ Feature: GetTreeQuery "isMatchedByFilter": true, "label": "My Node: a1", "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-a1-default\"}", - "nodeTypeLabel": "My Document Type" + "nodeTypeLabel": "My Document Type", + "isCreated": false, + "isModified": false, + "isRemoved": false } } } @@ -783,7 +882,10 @@ Feature: GetTreeQuery "isHiddenInMenu": false, "hasScheduledDisabledState": false, "hasUnloadedChildren": false, - "children": [] + "children": [], + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-a2-default\"}", @@ -795,9 +897,15 @@ Feature: GetTreeQuery "isHiddenInMenu": false, "hasScheduledDisabledState": false, "hasUnloadedChildren": false, - "children": [] + "children": [], + "isCreated": false, + "isModified": false, + "isRemoved": false } - ] + ], + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-b-disabled\"}", @@ -809,7 +917,10 @@ Feature: GetTreeQuery "isHiddenInMenu": false, "hasScheduledDisabledState": false, "hasUnloadedChildren": false, - "children": [] + "children": [], + "isCreated": false, + "isModified": false, + "isRemoved": false }, { "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-c-other-type\"}", @@ -821,7 +932,11 @@ Feature: GetTreeQuery "isHiddenInMenu": false, "hasScheduledDisabledState": false, "hasUnloadedChildren": true, - "children": [] + "children": [], + "isCreated": false, + "isModified": false, + "isRemoved": false + }, { "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"live\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"feature-d-multi-dsp\"}", @@ -833,9 +948,15 @@ Feature: GetTreeQuery "isHiddenInMenu": false, "hasScheduledDisabledState": false, "hasUnloadedChildren": false, - "children": [] + "children": [], + "isCreated": false, + "isModified": false, + "isRemoved": false } - ] + ], + "isCreated": false, + "isModified": false, + "isRemoved": false } } } diff --git a/Tests/Behavior/Features/Api/04-GetTreeQuery-WorkspaceChanges.feature b/Tests/Behavior/Features/Api/04-GetTreeQuery-WorkspaceChanges.feature new file mode 100644 index 0000000000..69ac35af0a --- /dev/null +++ b/Tests/Behavior/Features/Api/04-GetTreeQuery-WorkspaceChanges.feature @@ -0,0 +1,420 @@ +Feature: GetTreeQuery + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | language | de, en, gsw | gsw->de, en | + And using the following node types: + """yaml + 'Neos.Neos:Sites': + superTypes: + 'Neos.ContentRepository:Root': true + + 'Neos.Neos:Content': + abstract: true + + 'Neos.Neos:Document': + abstract: true + properties: + title: + type: string + + 'Neos.Neos:Site': + label: "${'Homepage ' + node.name}" + superTypes: + 'Neos.Neos:Document': true + ui: + icon: "globe" + label: "Home Page Type" + + 'Vendor.Site:Document': + label: "${Neos.Node.labelForNode(node).prefix('My Node: ').properties('title')}" + superTypes: + 'Neos.Neos:Document': true + ui: + icon: "my-icon" + label: "My Document Type" + + 'Vendor.Site:OtherDocument': + label: "My Other Node" + superTypes: + 'Neos.Neos:Document': true + ui: + icon: "my-other-icon" + label: "My Other Document Type" + + 'Vendor.Site:Content': + superTypes: + 'Neos.Neos:Content': true + ui: + icon: "my-content" + label: "My 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 I am in workspace "live" and dimension space point {"language": "en"} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "sites" | + | nodeTypeName | "Neos.Neos:Sites" | + + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | originDimensionSpacePoint | nodeName | + | homepage | sites | Neos.Neos:Site | {"title": "home"} | {"language": "en"} | site-a | + | features | homepage | Vendor.Site:Document | {"title": "features"} | {"language": "en"} | features | + | features-content | features | Vendor.Site:Content | {} | {"language": "en"} | | + | feature-a-default | features | Vendor.Site:Document | {"title": "a"} | {"language": "en"} | a | + | feature-a1-default | feature-a-default | Vendor.Site:Document | {"title": "a1"} | {"language": "en"} | leaf | + | feature-a2-default | feature-a-default | Vendor.Site:Document | {"title": "a2"} | {"language": "en"} | | + | feature-b-disabled | features | Vendor.Site:Document | {"title": "b"} | {"language": "en"} | | + | feature-c-other-type | features | Vendor.Site:OtherDocument | {"title": "c"} | {"language": "en"} | | + | feature-c1-default | feature-c-other-type | Vendor.Site:Document | {"title": "c1"} | {"language": "en"} | | + | feature-d-multi-dsp | features | Vendor.Site:Document | {"title": "d"} | {"language": "en"} | d | + | search | homepage | Vendor.Site:Document | {"title": "search"} | {"language": "en"} | search | + | search-content | search | Vendor.Site:Content | {} | {"language": "en"} | | + | search-a-default | search | Vendor.Site:Document | {"title": "a"} | {"language": "en"} | | + | search-a1-default | search-a-default | Vendor.Site:Document | {"title": "a1"} | {"language": "en"} | | + | search-a2-other-type | search-a-default | Vendor.Site:OtherDocument | {"title": "a2"} | {"language": "en"} | | + | search-a3-other-text | search-a-default | Vendor.Site:OtherDocument | {"title": "a3 special text"} | {"language": "en"} | | + | search-b-with-text | search | Vendor.Site:Document | {"title": "b special text"} | {"language": "en"} | | + | search-c-other-type | search | Vendor.Site:OtherDocument | {"title": "c"} | {"language": "en"} | | + + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "homepage" | + | sourceOrigin | {"language":"en"} | + | targetOrigin | {"language":"de"} | + + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "features" | + | sourceOrigin | {"language":"en"} | + | targetOrigin | {"language":"de"} | + + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "feature-d-multi-dsp" | + | sourceOrigin | {"language":"en"} | + | targetOrigin | {"language":"de"} | + + And the command SetNodeProperties is executed with payload: + | Key | Value | + | nodeAggregateId | "features" | + | originDimensionSpacePoint | {"language": "de"} | + | propertyValues | {"title": "features (de)"} | + + And the command SetNodeProperties is executed with payload: + | Key | Value | + | nodeAggregateId | "feature-d-multi-dsp" | + | originDimensionSpacePoint | {"language": "de"} | + | propertyValues | {"title": "d (de)"} | + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "feature-b-disabled" | + | nodeVariantSelectionStrategy | "allVariants" | + | tag | "disabled" | + + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-test" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-identifier" | + + And I am in workspace "user-test" and dimension space point {"language": "en"} + + Scenario: Simplest GetTreeQuery before changes are applied + + When I issue the following query to "http://127.0.0.1:8081/neos/ui-services/get-tree": + | Key | Value | + | startingPoint | '{"contentRepositoryId":"default","workspaceName":"user-test","dimensionSpacePoint":{"language":"en"},"aggregateId":"homepage"}' | + | loadingDepth | 1 | + | baseNodeTypeFilter | "" | + | narrowNodeTypeFilter | null | + | searchTerm | null | + | selectedNodeId | null | + + Then I expect the following query response: + """json + { + "success": { + "root": { + "hasScheduledDisabledState": false, + "hasUnloadedChildren": false, + "icon": "globe", + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "label": "Homepage site-a", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"homepage\"}", + "nodeTypeLabel": "Home Page Type", + "isCreated": false, + "isModified": false, + "isRemoved": false, + "children": [ + { + "hasScheduledDisabledState": false, + "hasUnloadedChildren": true, + "children": [], + "icon": "my-icon", + "isCreated": false, + "isRemoved": false, + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "isModified": false, + "label": "My Node: features", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"features\"}", + "nodeTypeLabel": "My Document Type" + }, + { + "children": [], + "hasScheduledDisabledState": false, + "hasUnloadedChildren": true, + "icon": "my-icon", + "isCreated": false, + "isRemoved": false, + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "isModified": false, + "label": "My Node: search", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"search\"}", + "nodeTypeLabel": "My Document Type" + } + ] + } + } + } + """ + + Scenario: Ensure isCreated and isModified is detected for Nodes + + And the command CreateNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "nodus-plus" | + | nodeTypeName | "Vendor.Site:Document" | + | parentNodeAggregateId | "homepage" | + | nodeName | "child-document" | + | initialPropertyValues | {"title": "Extra Node - added"} | + + And the command SetNodeProperties is executed with payload: + | Key | Value | + | workspaceName | "user-test" | + | nodeAggregateId | "features" | + | originDimensionSpacePoint | {"language": "en"} | + | propertyValues | {"title": "features (en) - modified"} | + + When I issue the following query to "http://127.0.0.1:8081/neos/ui-services/get-tree": + | Key | Value | + | startingPoint | '{"contentRepositoryId":"default","workspaceName":"user-test","dimensionSpacePoint":{"language":"en"},"aggregateId":"homepage"}' | + | loadingDepth | 1 | + | baseNodeTypeFilter | "" | + | narrowNodeTypeFilter | null | + | searchTerm | null | + | selectedNodeId | null | + + Then I expect the following query response: + """json + { + "success": { + "root": { + "hasScheduledDisabledState": false, + "hasUnloadedChildren": false, + "icon": "globe", + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "label": "Homepage site-a", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"homepage\"}", + "nodeTypeLabel": "Home Page Type", + "isCreated": false, + "isModified": false, + "isRemoved": false, + "children": [ + { + "hasScheduledDisabledState": false, + "hasUnloadedChildren": true, + "children": [], + "icon": "my-icon", + "isCreated": false, + "isRemoved": false, + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "isModified": true, + "label": "My Node: features (en) - modified", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"features\"}", + "nodeTypeLabel": "My Document Type" + }, + { + "children": [], + "hasScheduledDisabledState": false, + "hasUnloadedChildren": true, + "icon": "my-icon", + "isCreated": false, + "isRemoved": false, + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "isModified": false, + "label": "My Node: search", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"search\"}", + "nodeTypeLabel": "My Document Type" + }, + { + "children": [], + "hasScheduledDisabledState": false, + "hasUnloadedChildren": false, + "icon": "my-icon", + "isCreated": true, + "isRemoved": false, + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "isModified": true, + "label": "My Node: Extra Node - added", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"nodus-plus\"}", + "nodeTypeLabel": "My Document Type" + } + ] + } + } + } + """ + + Scenario: Deleted nodes are returned but not their children + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "features" | + | nodeVariantSelectionStrategy | "allVariants" | + | tag | "removed" | + + When I issue the following query to "http://127.0.0.1:8081/neos/ui-services/get-tree": + | Key | Value | + | startingPoint | '{"contentRepositoryId":"default","workspaceName":"user-test","dimensionSpacePoint":{"language":"en"},"aggregateId":"homepage"}' | + | loadingDepth | 2 | + | baseNodeTypeFilter | "" | + | narrowNodeTypeFilter | null | + | searchTerm | null | + | selectedNodeId | null | + + Then I expect the following query response: + """json + { + "success": { + "root": { + "hasScheduledDisabledState": false, + "hasUnloadedChildren": false, + "icon": "globe", + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "label": "Homepage site-a", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"homepage\"}", + "nodeTypeLabel": "Home Page Type", + "isCreated": false, + "isModified": false, + "isRemoved": false, + "children": [ + { + "hasScheduledDisabledState": false, + "hasUnloadedChildren": false, + "children": [], + "icon": "my-icon", + "isCreated": false, + "isRemoved": true, + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "isModified": true, + "label": "My Node: features", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"features\"}", + "nodeTypeLabel": "My Document Type" + }, + { + "hasScheduledDisabledState": false, + "hasUnloadedChildren": false, + "icon": "my-icon", + "isCreated": false, + "isRemoved": false, + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "isModified": false, + "label": "My Node: search", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"search\"}", + "nodeTypeLabel": "My Document Type", + "children": [ + { + "children": [], + "hasScheduledDisabledState": false, + "hasUnloadedChildren": false, + "icon": "my-content", + "isCreated": false, + "isRemoved": false, + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "isModified": false, + "label": "Vendor.Site:Content", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"search-content\"}", + "nodeTypeLabel": "My Content" + }, + { + "children": [], + "hasScheduledDisabledState": false, + "hasUnloadedChildren": true, + "icon": "my-icon", + "isCreated": false, + "isRemoved": false, + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "isModified": false, + "label": "My Node: a", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"search-a-default\"}", + "nodeTypeLabel": "My Document Type" + }, + { + "children": [], + "hasScheduledDisabledState": false, + "hasUnloadedChildren": false, + "icon": "my-icon", + "isCreated": false, + "isRemoved": false, + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "isModified": false, + "label": "My Node: b special text", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"search-b-with-text\"}", + "nodeTypeLabel": "My Document Type" + }, + { + "children": [], + "hasScheduledDisabledState": false, + "hasUnloadedChildren": false, + "icon": "my-other-icon", + "isCreated": false, + "isRemoved": false, + "isDisabled": false, + "isHiddenInMenu": false, + "isMatchedByFilter": true, + "isModified": false, + "label": "My Other Node", + "nodeAddress": "{\"contentRepositoryId\":\"default\",\"workspaceName\":\"user-test\",\"dimensionSpacePoint\":{\"language\":\"en\"},\"aggregateId\":\"search-c-other-type\"}", + "nodeTypeLabel": "My Other Document Type" + } + ] + } + ] + } + } + } + """