From 7a220deec823cc36d28012f8b9b34835608e4738 Mon Sep 17 00:00:00 2001 From: bwaidelich Date: Sun, 12 Nov 2023 11:52:22 +0100 Subject: [PATCH 001/214] WIP: FEATURE: Site configuration presets Resolves: #4582 --- Neos.Neos/Classes/Domain/Model/Site.php | 24 +++++++++++++-- Neos.Neos/Configuration/Settings.Sites.yaml | 18 +++++++++++ Neos.Neos/Configuration/Settings.yaml | 16 ---------- .../Migrations/Code/Version20230801154834.php | 4 +-- .../Features/FrontendRouting/Basic.feature | 5 ++-- .../FrontendRouting/Dimensions.feature | 30 +++++++++++-------- .../FrontendRouting/DisableNodes.feature | 5 ++-- .../FrontendRouting/MultiSiteLinking.feature | 11 +++++-- .../FrontendRouting/RouteCache.feature | 5 ++-- .../FrontendRouting/Shortcuts.feature | 5 ++-- .../TetheredSiteChildDocuments.feature | 5 ++-- .../Features/Fusion/ContentCase.feature | 5 ++-- .../Features/Fusion/ContentCollection.feature | 5 ++-- .../Features/Fusion/ConvertUris.feature | 5 ++-- .../Behavior/Features/Fusion/Menu.feature | 5 ++-- 15 files changed, 95 insertions(+), 53 deletions(-) create mode 100755 Neos.Neos/Configuration/Settings.Sites.yaml diff --git a/Neos.Neos/Classes/Domain/Model/Site.php b/Neos.Neos/Classes/Domain/Model/Site.php index 1d15d90a342..7ca8eca6efd 100644 --- a/Neos.Neos/Classes/Domain/Model/Site.php +++ b/Neos.Neos/Classes/Domain/Model/Site.php @@ -17,8 +17,10 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Neos\ContentRepositoryRegistry\Exception\InvalidConfigurationException; use Neos\Flow\Annotations as Flow; use Neos\Media\Domain\Model\AssetCollection; +use Neos\Utility\Arrays; /** * Domain model of a site @@ -35,12 +37,19 @@ class Site public const STATE_OFFLINE = 2; /** - * @Flow\InjectConfiguration(path="sites") * @var array * @phpstan-var array> */ + #[Flow\InjectConfiguration(path: 'sites')] protected $sitesConfiguration = []; + /** + * @var array + * @phpstan-var array> + */ + #[Flow\InjectConfiguration(path: 'sitePresets')] + protected $sitePresetsConfiguration = []; + /** * Name of the site * @@ -372,7 +381,16 @@ public function emitSiteChanged() public function getConfiguration(): SiteConfiguration { - // we DO NOT want recursive merge here - return SiteConfiguration::fromArray($this->sitesConfiguration[$this->nodeName] ?? $this->sitesConfiguration['*']); + $siteSettingsPath = array_key_exists($this->nodeName, $this->sitesConfiguration) ? $this->nodeName : '*'; + $siteSettings = $this->sitesConfiguration[$siteSettingsPath]; + if (isset($siteSettings['preset'])) { + is_string($siteSettings['preset']) || throw new \RuntimeException(sprintf('Invalid "preset" configuration for "Neos.Neos.sites.%s". Expected string, got: %s', $siteSettingsPath, get_debug_type($siteSettings['preset'])), 1699785648); + if (!isset($this->sitePresetsConfiguration[$siteSettings['preset']]) || !is_array($this->sitePresetsConfiguration[$siteSettings['preset']])) { + throw new \RuntimeException(sprintf('Site settings "Neos.Neos.sites.%s" refer to a preset "%s", but no corresponding preset is configured', $siteSettingsPath, $siteSettings['preset']), 1699785736); + } + $siteSettings = Arrays::arrayMergeRecursiveOverrule($this->sitePresetsConfiguration[$siteSettings['preset']], $siteSettings); + unset($siteSettings['preset']); + } + return SiteConfiguration::fromArray($siteSettings); } } diff --git a/Neos.Neos/Configuration/Settings.Sites.yaml b/Neos.Neos/Configuration/Settings.Sites.yaml new file mode 100755 index 00000000000..3c62589e72e --- /dev/null +++ b/Neos.Neos/Configuration/Settings.Sites.yaml @@ -0,0 +1,18 @@ +# # +# Site specific settings # +# # +# This file contains settings specific to Neos Sites # + +Neos: + + Neos: + sitePresets: + 'default': + uriPathSuffix: '.html' + contentRepository: default + contentDimensions: + resolver: + factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\AutoUriPathResolverFactory + sites: + '*': + preset: 'default' diff --git a/Neos.Neos/Configuration/Settings.yaml b/Neos.Neos/Configuration/Settings.yaml index 4f45714082b..6580ceda555 100755 --- a/Neos.Neos/Configuration/Settings.yaml +++ b/Neos.Neos/Configuration/Settings.yaml @@ -11,9 +11,6 @@ Neos: Neos: - contentDimensions: - resolution: - uriPathSegmentDelimiter: '_' fusion: # if set to true, Fusion is cached on a per-site basis. @@ -54,19 +51,6 @@ Neos: More information and contribution opportunities at https://www.neos.io --> - routing: - # Setting this to true allows to use an empty uriSegment for default dimensions. - # The only limitation is that all segments must be unique across all dimenions. - supportEmptySegmentForDimensions: true - - sites: - '*': - uriPathSuffix: '.html' - contentRepository: default - contentDimensions: - resolver: - factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\AutoUriPathResolverFactory - nodeTypes: groups: general: diff --git a/Neos.Neos/Migrations/Code/Version20230801154834.php b/Neos.Neos/Migrations/Code/Version20230801154834.php index 68118a7648c..f31c5610840 100644 --- a/Neos.Neos/Migrations/Code/Version20230801154834.php +++ b/Neos.Neos/Migrations/Code/Version20230801154834.php @@ -4,7 +4,7 @@ /** - * Replace defaultUriSuffix configuration with uriPathSuffix in default site configuration + * Replace defaultUriSuffix configuration with uriPathSuffix in default site preset configuration */ class Version20230801154834 extends AbstractMigration { @@ -16,6 +16,6 @@ public function getIdentifier(): string public function up(): void { - $this->moveSettingsPaths(['Neos', 'Flow', 'mvc', 'routes', 'Neos.Neos', 'variables', 'defaultUriSuffix'], ['Neos', 'Neos', 'sites', '*', 'uriPathSuffix']); + $this->moveSettingsPaths(['Neos', 'Flow', 'mvc', 'routes', 'Neos.Neos', 'variables', 'defaultUriSuffix'], ['Neos', 'Neos', 'sitePresets', 'default', 'uriPathSuffix']); } } diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Basic.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Basic.feature index da5635f69db..a2c65cacb27 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Basic.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Basic.feature @@ -59,8 +59,9 @@ Feature: Basic routing functionality (match & resolve document nodes in one dime Neos: Neos: sites: - '*': - contentRepository: default + 'node1': + preset: 'default' + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Dimensions.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Dimensions.feature index c6a9da1b7bf..a0ff8031f22 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Dimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Dimensions.feature @@ -74,8 +74,9 @@ Feature: Routing functionality with multiple content dimensions Neos: Neos: sites: - '*': - contentRepository: default + 'node1': + preset: default + uriPathSuffix: '' contentDimensions: defaultDimensionSpacePoint: market: DE @@ -114,8 +115,9 @@ Feature: Routing functionality with multiple content dimensions Neos: Neos: sites: - '*': - contentRepository: default + 'node1': + preset: default + uriPathSuffix: '' contentDimensions: defaultDimensionSpacePoint: market: DE @@ -173,8 +175,9 @@ Feature: Routing functionality with multiple content dimensions Neos: Neos: sites: - '*': - contentRepository: default + 'node1': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\UriPathResolverFactory @@ -242,8 +245,9 @@ Feature: Routing functionality with multiple content dimensions Neos: Neos: sites: - '*': - contentRepository: default + 'node1': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\UriPathResolverFactory @@ -309,8 +313,9 @@ Feature: Routing functionality with multiple content dimensions Neos: Neos: sites: - '*': - contentRepository: default + 'node1': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\UriPathResolverFactory @@ -373,8 +378,9 @@ Feature: Routing functionality with multiple content dimensions Neos: Neos: sites: - '*': - contentRepository: default + 'node1': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\UriPathResolverFactory diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/DisableNodes.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/DisableNodes.feature index 8912929e8f3..67900f786df 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/DisableNodes.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/DisableNodes.feature @@ -60,8 +60,9 @@ Feature: Routing behavior of removed, disabled and re-enabled nodes Neos: Neos: sites: - '*': - contentRepository: default + 'node1': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MultiSiteLinking.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MultiSiteLinking.feature index 33b0b36b404..e221ad3e615 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MultiSiteLinking.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MultiSiteLinking.feature @@ -66,8 +66,15 @@ Feature: Linking between multiple websites Neos: Neos: sites: - '*': - contentRepository: default + 'site-1': + preset: default + uriPathSuffix: '' + contentDimensions: + resolver: + factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory + 'site-2': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/RouteCache.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/RouteCache.feature index 4a5df7fc03d..9d905eb6e89 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/RouteCache.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/RouteCache.feature @@ -58,8 +58,9 @@ Feature: Route cache invalidation Neos: Neos: sites: - '*': - contentRepository: default + 'node1': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Shortcuts.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Shortcuts.feature index 3bba3d61557..e05f925bf78 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Shortcuts.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Shortcuts.feature @@ -88,8 +88,9 @@ Feature: Routing behavior of shortcut nodes Neos: Neos: sites: - '*': - contentRepository: default + 'node1': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/TetheredSiteChildDocuments.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/TetheredSiteChildDocuments.feature index 5b644025b96..1c8045f71e3 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/TetheredSiteChildDocuments.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/TetheredSiteChildDocuments.feature @@ -47,8 +47,9 @@ Feature: Tests for site node child documents. These are special in that they hav Neos: Neos: sites: - '*': - contentRepository: default + 'site': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCase.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCase.feature index 3c18ebada65..cd07163192b 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCase.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCase.feature @@ -50,8 +50,9 @@ Feature: Tests for the "Neos.Neos:ContentCase" Fusion prototype Neos: Neos: sites: - '*': - contentRepository: default + 'a': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCollection.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCollection.feature index c834e9cb06e..2682199fdf5 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCollection.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCollection.feature @@ -57,8 +57,9 @@ Feature: Tests for the "Neos.Neos:ContentCollection" Fusion prototype Neos: Neos: sites: - '*': - contentRepository: default + 'a': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/ConvertUris.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/ConvertUris.feature index f9b0575cabe..8b25a1f88eb 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/ConvertUris.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/ConvertUris.feature @@ -47,8 +47,9 @@ Feature: Tests for the "Neos.Neos:ConvertUris" Fusion prototype Neos: Neos: sites: - '*': - contentRepository: default + 'a': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature index 9d94f027ac5..c552bdea7c5 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature @@ -72,8 +72,9 @@ Feature: Tests for the "Neos.Neos:Menu" and related Fusion prototypes Neos: Neos: sites: - '*': - contentRepository: default + 'a': + preset: default + uriPathSuffix: '' contentDimensions: resolver: factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory From 53a0d132722c51ef600e97bfc256b408856640d8 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 13 Jan 2024 18:14:46 +0100 Subject: [PATCH 002/214] TASK make phpstan happy --- Neos.Neos/Classes/Domain/Model/Site.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Neos.Neos/Classes/Domain/Model/Site.php b/Neos.Neos/Classes/Domain/Model/Site.php index 7ca8eca6efd..b1d1fac85fd 100644 --- a/Neos.Neos/Classes/Domain/Model/Site.php +++ b/Neos.Neos/Classes/Domain/Model/Site.php @@ -17,7 +17,6 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use Neos\ContentRepositoryRegistry\Exception\InvalidConfigurationException; use Neos\Flow\Annotations as Flow; use Neos\Media\Domain\Model\AssetCollection; use Neos\Utility\Arrays; @@ -45,7 +44,7 @@ class Site /** * @var array - * @phpstan-var array> + * @phpstan-var array */ #[Flow\InjectConfiguration(path: 'sitePresets')] protected $sitePresetsConfiguration = []; From a209f3a02289f6025709d2577933e47673ea42d7 Mon Sep 17 00:00:00 2001 From: Wilhelm Behncke Date: Sun, 21 May 2023 17:03:01 +0200 Subject: [PATCH 003/214] BUGFIX: Add base workspace to `nodeDiscarded` signal --- .../Classes/Domain/Service/PublishingService.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Neos.ContentRepository/Classes/Domain/Service/PublishingService.php b/Neos.ContentRepository/Classes/Domain/Service/PublishingService.php index 0c352dea3df..c0c8163cc7a 100644 --- a/Neos.ContentRepository/Classes/Domain/Service/PublishingService.php +++ b/Neos.ContentRepository/Classes/Domain/Service/PublishingService.php @@ -166,7 +166,8 @@ public function discardNode(NodeInterface $node) */ protected function doDiscardNode(NodeInterface $node, array &$alreadyDiscardedNodeIdentifiers = []) { - if ($node->getWorkspace()->getBaseWorkspace() === null) { + $baseWorkspace = $node->getWorkspace()->getBaseWorkspace(); + if ($baseWorkspace === null) { throw new WorkspaceException('Nodes in a workspace without a base workspace cannot be discarded.', 1395841899); } if ($node->getPath() === '/') { @@ -197,7 +198,7 @@ protected function doDiscardNode(NodeInterface $node, array &$alreadyDiscardedNo } $this->nodeDataRepository->remove($node); - $this->emitNodeDiscarded($node); + $this->emitNodeDiscarded($node, $baseWorkspace); } /** @@ -274,11 +275,12 @@ public function emitNodePublished(NodeInterface $node, Workspace $targetWorkspac * The signal emits the node that has been discarded. * * @param NodeInterface $node + * @param Workspace|null $baseWorkspace * @return void * @Flow\Signal * @api */ - public function emitNodeDiscarded(NodeInterface $node) + public function emitNodeDiscarded(NodeInterface $node, ?Workspace $baseWorkspace = null) { } From c99cd8b1b609d60fd6775f4dd16f381664dc0f63 Mon Sep 17 00:00:00 2001 From: Wilhelm Behncke Date: Tue, 23 May 2023 16:38:10 +0200 Subject: [PATCH 004/214] BUGFIX: Ensure that ContentCacheFlusher registers all tags for actual node variant in workspace --- .../Classes/Fusion/Cache/ContentCacheFlusher.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index f2359b327cd..a50b8c19857 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -157,6 +157,20 @@ protected function addTagToFlush(string $tag, string $message = ''): void protected function registerAllTagsToFlushForNodeInWorkspace(NodeInterface $node, Workspace $workspace): void { + // Ensure that we're dealing with the variant of the given node that actually + // lives in the given workspace + if ($node->getWorkspace()->getName() !== $workspace->getName()) { + $workspaceContext = $this->contextFactory->create( + array_merge( + $node->getContext()->getProperties(), + ['workspaceName' => $workspace->getName()] + ) + ); + $node = $workspaceContext->getNodeByIdentifier($node->getIdentifier()); + if ($node === null) { + return; + } + } $nodeIdentifier = $node->getIdentifier(); if (!array_key_exists($workspace->getName(), $this->workspacesToFlush) || is_array($this->workspacesToFlush[$workspace->getName()]) === false) { From 23dd56510e2aea97174126ab591e7885988fdb99 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Fri, 1 Dec 2023 14:20:14 +0100 Subject: [PATCH 005/214] BUGFIX Flush cache when asset has changed --- .../Fusion/Cache/ContentCacheFlusher.php | 61 ++++++------------- 1 file changed, 18 insertions(+), 43 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index 077e2e7aa63..c123bf5b02d 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -27,6 +27,10 @@ use Neos\Media\Domain\Model\AssetVariantInterface; use Psr\Log\LoggerInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\Media\Domain\Service\AssetService; +use Neos\Neos\AssetUsage\Dto\AssetUsageReference; +use Neos\Flow\Persistence\PersistenceManagerInterface; +use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; /** * This service flushes Fusion content caches triggered by node changes. @@ -36,17 +40,19 @@ * This is the relevant case if publishing a workspace * - where we f.e. need to flush the cache for Live. * - * @Flow\Scope("singleton") */ +#[Flow\Scope('singleton')] class ContentCacheFlusher { #[Flow\InjectConfiguration(path: "fusion.contentCacheDebugMode")] protected bool $debugMode; - public function __construct( - protected ContentCache $contentCache, - protected LoggerInterface $systemLogger, + protected readonly ContentCache $contentCache, + protected readonly LoggerInterface $systemLogger, + protected readonly AssetService $assetService, + protected readonly PersistenceManagerInterface $persistenceManager, + protected readonly ContentRepositoryRegistry $contentRepositoryRegistry, ) { } @@ -251,55 +257,24 @@ public function registerAssetChange(AssetInterface $asset): void { // In Nodes only assets are referenced, never asset variants directly. When an asset // variant is updated, it is passed as $asset, but since it is never "used" by any node - // no flushing of corresponding entries happens. Thus we instead us the original asset + // no flushing of corresponding entries happens. Thus we instead use the original asset // of the variant. if ($asset instanceof AssetVariantInterface) { $asset = $asset->getOriginalAsset(); } - // TODO: re-implement this based on the code below - - /* - if (!$asset->isInUse()) { + if (!$this->assetService->isInUse($asset)) { return; } - $cachingHelper = $this->getCachingHelper(); - + $tagsToFlush = []; foreach ($this->assetService->getUsageReferences($asset) as $reference) { - if (!$reference instanceof AssetUsageInNodeProperties) { + if (!$reference instanceof AssetUsageReference) { continue; } - - $workspaceHash = $cachingHelper->renderWorkspaceTagForContextNode($reference->getWorkspaceName()); - $this->securityContext->withoutAuthorizationChecks(function () use ($reference, &$node) { - $node = $this->getContextForReference($reference)->getNodeByIdentifier($reference->getNodeIdentifier()); - }); - - if (!$node instanceof Node) { - $this->systemLogger->warning(sprintf( - 'Found a node reference from node with identifier %s in workspace %s to asset %s,' - . ' but the node could not be fetched.', - $reference->getNodeIdentifier(), - $reference->getWorkspaceName(), - $this->persistenceManager->getIdentifierByObject($asset) - ), LogEnvironment::fromMethodName(__METHOD__)); - continue; - } - - $this->registerNodeChange($node); - - $assetIdentifier = $this->persistenceManager->getIdentifierByObject($asset); - // @see RuntimeContentCache.addTag - $tagName = 'AssetDynamicTag_' . $workspaceHash . '_' . $assetIdentifier; - $this->addTagToFlush( - $tagName, - sprintf( - 'which were tagged with "%s" because asset "%s" has changed.', - $tagName, - $assetIdentifier - ) - ); - }*/ + $contentRepository = $this->contentRepositoryRegistry->get($reference->getContentRepositoryId()); + $this->flushNodeAggregate($contentRepository, $reference->getContentStreamId(), $reference->getNodeAggregateId()); + } + $this->flushTags($tagsToFlush); } } From b353adcf12dfec7797bc6745461fcf340da72926 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sun, 17 Mar 2024 10:31:05 +0100 Subject: [PATCH 006/214] BUGFIX Flush cache when asset has changed --- .../Fusion/Cache/ContentCacheFlusher.php | 117 +++++++++++------- 1 file changed, 73 insertions(+), 44 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index c123bf5b02d..9e4e4de866f 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -27,10 +27,9 @@ use Neos\Media\Domain\Model\AssetVariantInterface; use Psr\Log\LoggerInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; -use Neos\Media\Domain\Service\AssetService; -use Neos\Neos\AssetUsage\Dto\AssetUsageReference; -use Neos\Flow\Persistence\PersistenceManagerInterface; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; +use Neos\Neos\AssetUsage\Dto\AssetUsageFilter; +use Neos\Neos\AssetUsage\GlobalAssetUsageService; /** * This service flushes Fusion content caches triggered by node changes. @@ -47,11 +46,15 @@ class ContentCacheFlusher #[Flow\InjectConfiguration(path: "fusion.contentCacheDebugMode")] protected bool $debugMode; + /** + * @var array + */ + private array $tagsToFlushOnShutdown = []; + public function __construct( protected readonly ContentCache $contentCache, protected readonly LoggerInterface $systemLogger, - protected readonly AssetService $assetService, - protected readonly PersistenceManagerInterface $persistenceManager, + protected readonly GlobalAssetUsageService $globalAssetUsageService, protected readonly ContentRepositoryRegistry $contentRepositoryRegistry, ) { } @@ -68,28 +71,41 @@ public function flushNodeAggregate( ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId ): void { - $tagsToFlush = []; - $tagsToFlush[ContentCache::TAG_EVERYTHING] = 'which were tagged with "Everything".'; - $this->registerChangeOnNodeIdentifier($contentRepository->id, $contentStreamId, $nodeAggregateId, $tagsToFlush); + $tagsToFlush = array_merge( + $this->collectTagsForChangeOnNodeAggregate($contentRepository, $contentStreamId, $nodeAggregateId), + $tagsToFlush + ); + + $this->flushTags($tagsToFlush); + } + + /** + * @return array + */ + protected function collectTagsForChangeOnNodeAggregate( + ContentRepository $contentRepository, + ContentStreamId $contentStreamId, + NodeAggregateId $nodeAggregateId + ): array { $nodeAggregate = $contentRepository->getContentGraph()->findNodeAggregateById( $contentStreamId, $nodeAggregateId ); if (!$nodeAggregate) { // Node Aggregate was removed in the meantime, so no need to clear caches on this one anymore. - return; + return []; } + $tagsToFlush = $this->collectTagsForChangeOnNodeIdentifier($contentRepository->id, $contentStreamId, $nodeAggregateId); - $this->registerChangeOnNodeType( + $tagsToFlush = array_merge($this->collectTagsForChangeOnNodeType( $nodeAggregate->nodeTypeName, $contentRepository->id, $contentStreamId, $nodeAggregateId, - $tagsToFlush, $contentRepository - ); + ), $tagsToFlush); $parentNodeAggregates = []; foreach ( @@ -136,24 +152,20 @@ public function flushNodeAggregate( $parentNodeAggregates[] = $parentNodeAggregate; } } - $this->flushTags($tagsToFlush); + + return $tagsToFlush; } /** - * Please use registerNodeChange() if possible. This method is a low-level api. If you do use this method make sure - * that $cacheIdentifier contains the workspacehash as well as the node identifier: - * $workspaceHash .'_'. $nodeIdentifier - * The workspacehash can be received via $this->getCachingHelper()->renderWorkspaceTagForContextNode($workpsacename) - * - * @param array &$tagsToFlush + * @return array */ - private function registerChangeOnNodeIdentifier( + private function collectTagsForChangeOnNodeIdentifier( ContentRepositoryId $contentRepositoryId, ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId, - array &$tagsToFlush - ): void { + ): array { + $tagsToFlush = []; $nodeCacheIdentifier = CacheTag::forNodeAggregate($contentRepositoryId, $contentStreamId, $nodeAggregateId); $tagsToFlush[$nodeCacheIdentifier->value] = sprintf( @@ -161,29 +173,28 @@ private function registerChangeOnNodeIdentifier( $nodeCacheIdentifier->value ); - $descandantOfNodeCacheIdentifier = CacheTag::forDescendantOfNode($contentRepositoryId, $contentStreamId, $nodeAggregateId); - $tagsToFlush[$descandantOfNodeCacheIdentifier->value] = sprintf( + $descendantOfNodeCacheIdentifier = CacheTag::forDescendantOfNode($contentRepositoryId, $contentStreamId, $nodeAggregateId); + $tagsToFlush[$descendantOfNodeCacheIdentifier->value] = sprintf( 'which were tagged with "%s" because node "%s" has changed.', - $descandantOfNodeCacheIdentifier->value, + $descendantOfNodeCacheIdentifier->value, $nodeCacheIdentifier->value ); + + return $tagsToFlush; } /** - * This is a low-level api. Please use registerNodeChange() if possible. Otherwise make sure that $nodeTypePrefix - * is set up correctly and contains the workspacehash wich can be received via - * $this->getCachingHelper()->renderWorkspaceTagForContextNode($workpsacename) - * - * @param array &$tagsToFlush + * @return array $tagsToFlush */ - private function registerChangeOnNodeType( + private function collectTagsForChangeOnNodeType( NodeTypeName $nodeTypeName, ContentRepositoryId $contentRepositoryId, ContentStreamId $contentStreamId, ?NodeAggregateId $referenceNodeIdentifier, - array &$tagsToFlush, ContentRepository $contentRepository - ): void { + ): array { + $tagsToFlush = []; + try { $nodeTypesNamesToFlush = $this->getAllImplementedNodeTypeNames( $contentRepository->getNodeTypeManager()->getNodeType($nodeTypeName) @@ -202,11 +213,13 @@ private function registerChangeOnNodeType( $nodeTypeName->value ); } + + return $tagsToFlush; } /** - * Flush caches according to the previously registered node changes. + * Flush caches according to the given tags. * * @param array $tagsToFlush */ @@ -263,18 +276,34 @@ public function registerAssetChange(AssetInterface $asset): void $asset = $asset->getOriginalAsset(); } - if (!$this->assetService->isInUse($asset)) { - return; - } - $tagsToFlush = []; - foreach ($this->assetService->getUsageReferences($asset) as $reference) { - if (!$reference instanceof AssetUsageReference) { - continue; + $filter = AssetUsageFilter::create() + ->withAsset($asset->getAssetSourceIdentifier()) + ->includeVariantsOfAsset(); + + 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->contentStreamId, + $usage->nodeAggregateId + ), + $tagsToFlush + ); } - $contentRepository = $this->contentRepositoryRegistry->get($reference->getContentRepositoryId()); - $this->flushNodeAggregate($contentRepository, $reference->getContentStreamId(), $reference->getNodeAggregateId()); } - $this->flushTags($tagsToFlush); + + $this->tagsToFlushOnShutdown = array_merge($tagsToFlush, $this->tagsToFlushOnShutdown); + } + + /** + * Flush caches according to the previously registered changes. + */ + public function shutdownObject(): void + { + $this->flushTags($this->tagsToFlushOnShutdown); + $this->tagsToFlushOnShutdown = []; } } From b8c7bcfed22779b7ebfd968f8b87d0b471b153de Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sun, 17 Mar 2024 13:12:41 +0100 Subject: [PATCH 007/214] BUGFIX Flush cache when asset has changed --- Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index 9e4e4de866f..4606deefe90 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -276,6 +276,10 @@ public function registerAssetChange(AssetInterface $asset): void $asset = $asset->getOriginalAsset(); } + if ($asset->getAssetSourceIdentifier() === null) { + return; + } + $tagsToFlush = []; $filter = AssetUsageFilter::create() ->withAsset($asset->getAssetSourceIdentifier()) From e2e3ab33c2a302118e5aff3b271dd993ad579776 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sun, 17 Mar 2024 13:21:55 +0100 Subject: [PATCH 008/214] BUGFIX Flush cache when asset has changed --- .../Classes/Fusion/Cache/ContentCacheFlusher.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index 4606deefe90..3e52f3930d4 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -15,21 +15,21 @@ namespace Neos\Neos\Fusion\Cache; use Neos\ContentRepository\Core\ContentRepository; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeName; -use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; +use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; -use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\Fusion\Core\Cache\ContentCache; use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Model\AssetVariantInterface; -use Psr\Log\LoggerInterface; -use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; -use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Neos\AssetUsage\Dto\AssetUsageFilter; use Neos\Neos\AssetUsage\GlobalAssetUsageService; +use Psr\Log\LoggerInterface; /** * This service flushes Fusion content caches triggered by node changes. From 495183ec1e1ea82dfc1412162f5d67be1fa4a832 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sun, 17 Mar 2024 20:55:51 +0100 Subject: [PATCH 009/214] BUGFIX Flush cache when asset has changed --- Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index 3e52f3930d4..2d4db6b7794 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -24,6 +24,7 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; +use Neos\Flow\Persistence\PersistenceManagerInterface; use Neos\Fusion\Core\Cache\ContentCache; use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Model\AssetVariantInterface; @@ -56,6 +57,7 @@ public function __construct( protected readonly LoggerInterface $systemLogger, protected readonly GlobalAssetUsageService $globalAssetUsageService, protected readonly ContentRepositoryRegistry $contentRepositoryRegistry, + protected readonly PersistenceManagerInterface $persistenceManager, ) { } @@ -276,13 +278,9 @@ public function registerAssetChange(AssetInterface $asset): void $asset = $asset->getOriginalAsset(); } - if ($asset->getAssetSourceIdentifier() === null) { - return; - } - $tagsToFlush = []; $filter = AssetUsageFilter::create() - ->withAsset($asset->getAssetSourceIdentifier()) + ->withAsset($this->persistenceManager->getIdentifierByObject($asset)) ->includeVariantsOfAsset(); foreach ($this->globalAssetUsageService->findByFilter($filter) as $contentRepositoryId => $usages) { From ee7c85b76d3504ccb9063bfe36fb9b5edfbf3481 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Fri, 29 Mar 2024 16:14:38 +0100 Subject: [PATCH 010/214] Catch not completely covered node aggregates when resolving structure adjustments --- .../src/Adjustment/DimensionAdjustment.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DimensionAdjustment.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DimensionAdjustment.php index b35ce49b8f7..554519dad57 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DimensionAdjustment.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DimensionAdjustment.php @@ -5,6 +5,7 @@ namespace Neos\ContentRepository\StructureAdjustment\Adjustment; use Neos\ContentRepository\Core\DimensionSpace\InterDimensionalVariationGraph; +use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\VariantType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; @@ -29,6 +30,16 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): iterable return []; } if ($nodeType->isOfType(NodeTypeName::ROOT_NODE_TYPE_NAME)) { + foreach ($this->projectedNodeIterator->nodeAggregatesOfType($nodeTypeName) as $nodeAggregate) { + if ( + !$nodeAggregate->coveredDimensionSpacePoints->equals($this->interDimensionalVariationGraph->getDimensionSpacePoints()) + ) { + throw new \Exception( + 'Cannot determine structure adjustments for root node type ' . $nodeTypeName->value + . ', run UpdateRootNodeAggregateDimensions first' + ); + } + } return []; } foreach ($this->projectedNodeIterator->nodeAggregatesOfType($nodeTypeName) as $nodeAggregate) { From db33506291a54fa9a1e8e8f69309f66f77ac8852 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Wed, 3 Apr 2024 23:27:58 +0200 Subject: [PATCH 011/214] 4742 - Use interdimensional siblings to fix DocumentUriPathProjection --- .../Projection/DocumentNodeInfo.php | 9 + .../Projection/DocumentUriPathFinder.php | 22 ++ .../Projection/DocumentUriPathProjection.php | 67 ++-- .../NodeCreationEdgeCases.feature | 84 +++++ .../NodeVariationEdgeCases.feature | 300 ++++++++++++++++++ 5 files changed, 459 insertions(+), 23 deletions(-) create mode 100644 Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeCreationEdgeCases.feature create mode 100644 Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeVariationEdgeCases.feature diff --git a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentNodeInfo.php b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentNodeInfo.php index 0a092055bdb..8caa59173b0 100644 --- a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentNodeInfo.php +++ b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentNodeInfo.php @@ -71,6 +71,15 @@ public function withOriginDimensionSpacePoint(OriginDimensionSpacePoint $originD return new self($source); } + public function withoutSiblings(): self + { + $source = $this->source; + $source['precedingnodeaggregateid'] = null; + $source['succeedingnodeaggregateid'] = null; + + return new self($source); + } + public function getNodeAggregateId(): NodeAggregateId { return NodeAggregateId::fromString($this->source['nodeaggregateid']); diff --git a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathFinder.php b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathFinder.php index 79a0093790f..8c78fc04108 100644 --- a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathFinder.php +++ b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathFinder.php @@ -195,6 +195,28 @@ public function getLastChildNode( ); } + /** + * @throws NodeNotFoundException + * @internal + */ + public function getLastChildNodeNotBeing( + NodeAggregateId $parentNodeAggregateId, + string $dimensionSpacePointHash, + NodeAggregateId $excludedNodeAggregateId + ): DocumentNodeInfo { + return $this->fetchSingle( + 'dimensionSpacePointHash = :dimensionSpacePointHash + AND parentNodeAggregateId = :parentNodeAggregateId + AND nodeAggregateId != :excludedNodeAggregateId + AND succeedingNodeAggregateId IS NULL', + [ + 'dimensionSpacePointHash' => $dimensionSpacePointHash, + 'parentNodeAggregateId' => $parentNodeAggregateId->value, + 'excludedNodeAggregateId' => $excludedNodeAggregateId->value + ] + ); + } + /** * @api */ diff --git a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php index c6e503d57c8..6020e083a0e 100644 --- a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php +++ b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php @@ -8,9 +8,9 @@ use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Types\Types; -use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\EventInterface; +use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSiblings; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Event\DimensionShineThroughWasAdded; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Event\DimensionSpacePointWasMoved; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; @@ -394,7 +394,7 @@ private function whenNodePeerVariantWasCreated(NodePeerVariantWasCreated $event) $event->nodeAggregateId, $event->sourceOrigin, $event->peerOrigin, - $event->peerSucceedingSiblings->toDimensionSpacePointSet() + $event->peerSucceedingSiblings ); } @@ -407,7 +407,7 @@ private function whenNodeGeneralizationVariantWasCreated(NodeGeneralizationVaria $event->nodeAggregateId, $event->sourceOrigin, $event->generalizationOrigin, - $event->variantSucceedingSiblings->toDimensionSpacePointSet() + $event->variantSucceedingSiblings ); } @@ -420,7 +420,7 @@ private function whenNodeSpecializationVariantWasCreated(NodeSpecializationVaria $event->nodeAggregateId, $event->sourceOrigin, $event->specializationOrigin, - $event->specializationSiblings->toDimensionSpacePointSet() + $event->specializationSiblings ); } @@ -428,7 +428,7 @@ private function copyVariants( NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - DimensionSpacePointSet $coveredSpacePoints + InterdimensionalSiblings $interdimensionalSiblings, ): void { $sourceNode = $this->tryGetNode(fn () => $this->getState()->getByIdAndDimensionSpacePointHash( $nodeAggregateId, @@ -438,17 +438,18 @@ private function copyVariants( // Probably not a document node return; } - foreach ($coveredSpacePoints as $coveredSpacePoint) { + foreach ($interdimensionalSiblings as $interdimensionalSibling) { // Especially when importing a site it can happen that variants are created in a "non-deterministic" order, // so we need to first make sure a target variant doesn't exist: - $this->deleteNodeByIdAndDimensionSpacePointHash($nodeAggregateId, $coveredSpacePoint->hash); + $this->deleteNodeByIdAndDimensionSpacePointHash($nodeAggregateId, $interdimensionalSibling->dimensionSpacePoint->hash); - $this->insertNode( - $sourceNode - ->withDimensionSpacePoint($coveredSpacePoint) + $targetNode = $sourceNode + ->withDimensionSpacePoint($interdimensionalSibling->dimensionSpacePoint) ->withOriginDimensionSpacePoint($targetOrigin) - ->toArray() - ); + ->withoutSiblings(); + + $this->insertNode($targetNode->toArray()); + $this->connectNodeWithSiblings($targetNode, $targetNode->getParentNodeAggregateId(), $interdimensionalSibling->nodeAggregateId, false); } } @@ -657,7 +658,7 @@ private function moveNode( ): void { $this->disconnectNodeFromSiblings($node); - $this->connectNodeWithSiblings($node, $newParentNodeAggregateId, $newSucceedingNodeAggregateId); + $this->connectNodeWithSiblings($node, $newParentNodeAggregateId, $newSucceedingNodeAggregateId, true); if ($newParentNodeAggregateId->equals($node->getParentNodeAggregateId())) { return; @@ -908,10 +909,14 @@ private function disconnectNodeFromSiblings(DocumentNodeInfo $node): void } } + /** + * @param bool $includingSelf whether the node itself is to be considered its own sibling. true for moving, false for variation + */ private function connectNodeWithSiblings( DocumentNodeInfo $node, NodeAggregateId $parentNodeAggregateId, - ?NodeAggregateId $newSucceedingNodeAggregateId + ?NodeAggregateId $newSucceedingNodeAggregateId, + bool $includingSelf, ): void { if ($newSucceedingNodeAggregateId !== null) { $newPrecedingNode = $this->tryGetNode(fn () => $this->getState()->getPrecedingNode( @@ -927,24 +932,40 @@ private function connectNodeWithSiblings( ['precedingNodeAggregateId' => $node->getNodeAggregateId()->value] ); } else { - $newPrecedingNode = $this->tryGetNode(fn () => $this->getState()->getLastChildNode( - $parentNodeAggregateId, - $node->getDimensionSpacePointHash() - )); + $newPrecedingNode = $this->tryGetNode(fn () => $includingSelf + ? $this->getState()->getLastChildNode( + $parentNodeAggregateId, + $node->getDimensionSpacePointHash(), + ) + : $this->getState()->getLastChildNodeNotBeing( + $parentNodeAggregateId, + $node->getDimensionSpacePointHash(), + $node->getNodeAggregateId() + ) + ); } - if ($newPrecedingNode !== null) { + if ( + $newPrecedingNode !== null + && ($includingSelf || !$newPrecedingNode->getNodeAggregateId()->equals($node->getNodeAggregateId())) + ) { $this->updateNode( $newPrecedingNode, ['succeedingNodeAggregateId' => $node->getNodeAggregateId()->value] ); } - // update node itself - $this->updateNode($node, [ + $updatedNodeData = [ 'parentNodeAggregateId' => $parentNodeAggregateId->value, - 'precedingNodeAggregateId' => $newPrecedingNode?->getNodeAggregateId()->value, 'succeedingNodeAggregateId' => $newSucceedingNodeAggregateId?->value, - ]); + ]; + if ( + $includingSelf || !$newPrecedingNode?->getNodeAggregateId()->equals($node->getNodeAggregateId()) + ) { + $updatedNodeData['precedingNodeAggregateId'] = $newPrecedingNode?->getNodeAggregateId()->value; + } + + // update node itself + $this->updateNode($node, $updatedNodeData); } diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeCreationEdgeCases.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeCreationEdgeCases.feature new file mode 100644 index 00000000000..b69372be4c0 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeCreationEdgeCases.feature @@ -0,0 +1,84 @@ +@flowEntities @contentrepository +Feature: Test cases for node creation edge cases + + Scenario: Delete the succeeding sibling node in a virtual specialization and then create the node + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | example | source, spec, leafSpec | leafSpec -> spec -> source | + And using the following node types: + """yaml + 'Neos.Neos:Sites': + superTypes: + 'Neos.ContentRepository:Root': true + 'Neos.Neos:Document': + properties: + uriPathSegment: + type: string + 'Neos.Neos:Site': + superTypes: + 'Neos.Neos:Document': true + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And I am user identified by "initiating-user-identifier" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + And the graph projection is fully up to date + And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.Neos:Sites" | + And the graph projection is fully up to date + And the command CreateNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "shernode-homes" | + | nodeTypeName | "Neos.Neos:Site" | + | parentNodeAggregateId | "lady-eleonode-rootford" | + | originDimensionSpacePoint | {"example":"source"} | + And the graph projection is fully up to date + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | succeedingSiblingNodeAggregateId | nodeTypeName | initialPropertyValues | + # Let's prepare some siblings to check orderings. Also, everything gets better with siblings. + | elder-mc-nodeface | elder-document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "elder"} | + | eldest-mc-nodeface | eldest-document | shernode-homes | elder-mc-nodeface | Neos.Neos:Document | {"uriPathSegment": "eldest"} | + | younger-mc-nodeface | younger-document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "younger"} | + | youngest-mc-nodeface | youngest-document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "youngest"} | + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "younger-mc-nodeface" | + | coveredDimensionSpacePoint | {"example":"spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + When the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | succeedingSiblingNodeAggregateId | nodeTypeName | initialPropertyValues | + | nody-mc-nodeface | document | shernode-homes | younger-mc-nodeface | Neos.Neos:Document | {"uriPathSegment": "nody"} | + + Then I expect the documenturipath table to contain exactly: + # source: 65901ded4f068dac14ad0dce4f459b29 + # spec: 9a723c057afa02982dae9d0b541739be + # leafSpec: c60c44685475d0e2e4f2b964e6158ce2 + | dimensionspacepointhash | uripath | nodeaggregateidpath | nodeaggregateid | parentnodeaggregateid | precedingnodeaggregateid | succeedingnodeaggregateid | nodetypename | + | "65901ded4f068dac14ad0dce4f459b29" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "9a723c057afa02982dae9d0b541739be" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "65901ded4f068dac14ad0dce4f459b29" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "9a723c057afa02982dae9d0b541739be" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "65901ded4f068dac14ad0dce4f459b29" | "elder" | "lady-eleonode-rootford/shernode-homes/elder-mc-nodeface" | "elder-mc-nodeface" | "shernode-homes" | "eldest-mc-nodeface" | "nody-mc-nodeface" | "Neos.Neos:Document" | + | "9a723c057afa02982dae9d0b541739be" | "elder" | "lady-eleonode-rootford/shernode-homes/elder-mc-nodeface" | "elder-mc-nodeface" | "shernode-homes" | "eldest-mc-nodeface" | "nody-mc-nodeface" | "Neos.Neos:Document" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "elder" | "lady-eleonode-rootford/shernode-homes/elder-mc-nodeface" | "elder-mc-nodeface" | "shernode-homes" | "eldest-mc-nodeface" | "nody-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "eldest" | "lady-eleonode-rootford/shernode-homes/eldest-mc-nodeface" | "eldest-mc-nodeface" | "shernode-homes" | null | "elder-mc-nodeface" | "Neos.Neos:Document" | + | "9a723c057afa02982dae9d0b541739be" | "eldest" | "lady-eleonode-rootford/shernode-homes/eldest-mc-nodeface" | "eldest-mc-nodeface" | "shernode-homes" | null | "elder-mc-nodeface" | "Neos.Neos:Document" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "eldest" | "lady-eleonode-rootford/shernode-homes/eldest-mc-nodeface" | "eldest-mc-nodeface" | "shernode-homes" | null | "elder-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "nody" | "lady-eleonode-rootford/shernode-homes/nody-mc-nodeface" | "nody-mc-nodeface" | "shernode-homes" | "elder-mc-nodeface" | "younger-mc-nodeface" | "Neos.Neos:Document" | + | "9a723c057afa02982dae9d0b541739be" | "nody" | "lady-eleonode-rootford/shernode-homes/nody-mc-nodeface" | "nody-mc-nodeface" | "shernode-homes" | "elder-mc-nodeface" | "youngest-mc-nodeface" | "Neos.Neos:Document" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "nody" | "lady-eleonode-rootford/shernode-homes/nody-mc-nodeface" | "nody-mc-nodeface" | "shernode-homes" | "elder-mc-nodeface" | "youngest-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "younger" | "lady-eleonode-rootford/shernode-homes/younger-mc-nodeface" | "younger-mc-nodeface" | "shernode-homes" | "nody-mc-nodeface" | "youngest-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "youngest" | "lady-eleonode-rootford/shernode-homes/youngest-mc-nodeface" | "youngest-mc-nodeface" | "shernode-homes" | "younger-mc-nodeface" | null | "Neos.Neos:Document" | + | "9a723c057afa02982dae9d0b541739be" | "youngest" | "lady-eleonode-rootford/shernode-homes/youngest-mc-nodeface" | "youngest-mc-nodeface" | "shernode-homes" | "nody-mc-nodeface" | null | "Neos.Neos:Document" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "youngest" | "lady-eleonode-rootford/shernode-homes/youngest-mc-nodeface" | "youngest-mc-nodeface" | "shernode-homes" | "nody-mc-nodeface" | null | "Neos.Neos:Document" | diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeVariationEdgeCases.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeVariationEdgeCases.feature new file mode 100644 index 00000000000..fb562284490 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeVariationEdgeCases.feature @@ -0,0 +1,300 @@ +@flowEntities @contentrepository +Feature: Test cases for node variation edge cases + + Scenario: Create peer variant of node to dimension space point with specializations + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | example | source,peer,peerSpec | peerSpec->peer | + And using the following node types: + """yaml + 'Neos.Neos:Sites': + superTypes: + 'Neos.ContentRepository:Root': true + 'Neos.Neos:Document': + properties: + uriPathSegment: + type: string + 'Neos.Neos:Site': + superTypes: + 'Neos.Neos:Document': true + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And I am user identified by "initiating-user-identifier" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + And the graph projection is fully up to date + And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.Neos:Sites" | + And the graph projection is fully up to date + And the command CreateNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "shernode-homes" | + | nodeTypeName | "Neos.Neos:Site" | + | parentNodeAggregateId | "lady-eleonode-rootford" | + | originDimensionSpacePoint | {"example":"source"} | + And the graph projection is fully up to date + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "shernode-homes" | + | sourceOrigin | {"example":"source"} | + | targetOrigin | {"example":"peer"} | + And the graph projection is fully up to date + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | originDimensionSpacePoint | nodeName | parentNodeAggregateId | succeedingSiblingNodeAggregateId | nodeTypeName | initialPropertyValues | + # Set up our test subject document + | nody-mc-nodeface | {"example":"source"} | document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "nody"} | + # Let's create some siblings, both in source and target, to check ordering + | elder-mc-nodeface | {"example":"source"} | elder-document | shernode-homes | nody-mc-nodeface | Neos.Neos:Document | {"uriPathSegment": "elder"} | + | youngest-mc-nodeface | {"example":"source"} | youngest-document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "youngest"} | + | eldest-mc-nodeface | {"example":"peer"} | eldest-document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "eldest"} | + + When the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | sourceOrigin | {"example":"source"} | + | targetOrigin | {"example":"peer"} | + And the graph projection is fully up to date + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "elder-mc-nodeface" | + | sourceOrigin | {"example":"source"} | + | targetOrigin | {"example":"peer"} | + And the graph projection is fully up to date + # Complete the sibling set with a node in the target DSP between the middle and last node + And the command CreateNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "younger-mc-nodeface" | + | nodeTypeName | "Neos.Neos:Document" | + | parentNodeAggregateId | "shernode-homes" | + | originDimensionSpacePoint | {"example":"peer"} | + | nodeName | "younger-document" | + | initialPropertyValues | {"uriPathSegment": "younger"} | + And the graph projection is fully up to date + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | sourceOrigin | {"example":"source"} | + | targetOrigin | {"example":"peer"} | + And the graph projection is fully up to date + + Then I expect the documenturipath table to contain exactly: + # source: 65901ded4f068dac14ad0dce4f459b29 + # peer: fbe53ddc3305685fbb4dbf529f283a0e + # peerSpec: 2ca4fae2f65267c94c85602df0cbb728 + | dimensionspacepointhash | uripath | nodeaggregateidpath | nodeaggregateid | parentnodeaggregateid | precedingnodeaggregateid | succeedingnodeaggregateid | nodetypename | + | "2ca4fae2f65267c94c85602df0cbb728" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "65901ded4f068dac14ad0dce4f459b29" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "fbe53ddc3305685fbb4dbf529f283a0e" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "2ca4fae2f65267c94c85602df0cbb728" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "65901ded4f068dac14ad0dce4f459b29" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "fbe53ddc3305685fbb4dbf529f283a0e" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "2ca4fae2f65267c94c85602df0cbb728" | "elder" | "lady-eleonode-rootford/shernode-homes/elder-mc-nodeface" | "elder-mc-nodeface" | "shernode-homes" | "eldest-mc-nodeface" | "nody-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "elder" | "lady-eleonode-rootford/shernode-homes/elder-mc-nodeface" | "elder-mc-nodeface" | "shernode-homes" | null | "nody-mc-nodeface" | "Neos.Neos:Document" | + | "fbe53ddc3305685fbb4dbf529f283a0e" | "elder" | "lady-eleonode-rootford/shernode-homes/elder-mc-nodeface" | "elder-mc-nodeface" | "shernode-homes" | "eldest-mc-nodeface" | "nody-mc-nodeface" | "Neos.Neos:Document" | + | "2ca4fae2f65267c94c85602df0cbb728" | "eldest" | "lady-eleonode-rootford/shernode-homes/eldest-mc-nodeface" | "eldest-mc-nodeface" | "shernode-homes" | null | "elder-mc-nodeface" | "Neos.Neos:Document" | + | "fbe53ddc3305685fbb4dbf529f283a0e" | "eldest" | "lady-eleonode-rootford/shernode-homes/eldest-mc-nodeface" | "eldest-mc-nodeface" | "shernode-homes" | null | "elder-mc-nodeface" | "Neos.Neos:Document" | + | "2ca4fae2f65267c94c85602df0cbb728" | "nody" | "lady-eleonode-rootford/shernode-homes/nody-mc-nodeface" | "nody-mc-nodeface" | "shernode-homes" | "elder-mc-nodeface" | "younger-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "nody" | "lady-eleonode-rootford/shernode-homes/nody-mc-nodeface" | "nody-mc-nodeface" | "shernode-homes" | "elder-mc-nodeface" | "youngest-mc-nodeface" | "Neos.Neos:Document" | + | "fbe53ddc3305685fbb4dbf529f283a0e" | "nody" | "lady-eleonode-rootford/shernode-homes/nody-mc-nodeface" | "nody-mc-nodeface" | "shernode-homes" | "elder-mc-nodeface" | "younger-mc-nodeface" | "Neos.Neos:Document" | + | "2ca4fae2f65267c94c85602df0cbb728" | "younger" | "lady-eleonode-rootford/shernode-homes/younger-mc-nodeface" | "younger-mc-nodeface" | "shernode-homes" | "nody-mc-nodeface" | "youngest-mc-nodeface" | "Neos.Neos:Document" | + | "fbe53ddc3305685fbb4dbf529f283a0e" | "younger" | "lady-eleonode-rootford/shernode-homes/younger-mc-nodeface" | "younger-mc-nodeface" | "shernode-homes" | "nody-mc-nodeface" | "youngest-mc-nodeface" | "Neos.Neos:Document" | + | "2ca4fae2f65267c94c85602df0cbb728" | "youngest" | "lady-eleonode-rootford/shernode-homes/youngest-mc-nodeface" | "youngest-mc-nodeface" | "shernode-homes" | "younger-mc-nodeface" | null | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "youngest" | "lady-eleonode-rootford/shernode-homes/youngest-mc-nodeface" | "youngest-mc-nodeface" | "shernode-homes" | "nody-mc-nodeface" | null | "Neos.Neos:Document" | + | "fbe53ddc3305685fbb4dbf529f283a0e" | "youngest" | "lady-eleonode-rootford/shernode-homes/youngest-mc-nodeface" | "youngest-mc-nodeface" | "shernode-homes" | "younger-mc-nodeface" | null | "Neos.Neos:Document" | + + Scenario: Create generalization of node to dimension space point with further generalization and specializations + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | example | rootGeneral, general, source, specB | source -> general -> rootGeneral, specB -> general -> rootGeneral | + And using the following node types: + """yaml + 'Neos.Neos:Sites': + superTypes: + 'Neos.ContentRepository:Root': true + 'Neos.Neos:Document': + properties: + uriPathSegment: + type: string + 'Neos.Neos:Site': + superTypes: + 'Neos.Neos:Document': true + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And I am user identified by "initiating-user-identifier" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + And the graph projection is fully up to date + And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.Neos:Sites" | + And the graph projection is fully up to date + And the command CreateNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "shernode-homes" | + | nodeTypeName | "Neos.Neos:Site" | + | parentNodeAggregateId | "lady-eleonode-rootford" | + | originDimensionSpacePoint | {"example":"rootGeneral"} | + And the graph projection is fully up to date + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | originDimensionSpacePoint | nodeName | parentNodeAggregateId | succeedingSiblingNodeAggregateId | nodeTypeName | initialPropertyValues | + # Let's create some siblings, both in source and target, to check ordering + | eldest-mc-nodeface | {"example":"general"} | eldest-document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "eldest"} | + | nody-mc-nodeface | {"example":"source"} | document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "nody"} | + | elder-mc-nodeface | {"example":"source"} | elder-document | shernode-homes | nody-mc-nodeface | Neos.Neos:Document | {"uriPathSegment": "elder"} | + | younger-mc-nodeface | {"example":"general"} | younger-document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "younger"} | + | youngest-mc-nodeface | {"example":"source"} | youngest-document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "youngest"} | + + When the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | sourceOrigin | {"example":"source"} | + | targetOrigin | {"example":"general"} | + And the graph projection is fully up to date + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "elder-mc-nodeface" | + | sourceOrigin | {"example":"source"} | + | targetOrigin | {"example":"general"} | + And the graph projection is fully up to date + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | sourceOrigin | {"example":"source"} | + | targetOrigin | {"example":"general"} | + And the graph projection is fully up to date + + Then I expect the documenturipath table to contain exactly: + # general: 033e5de7b423f45bb4f5a09f73af839e + # source: 65901ded4f068dac14ad0dce4f459b29 + # sourceB: 9447118dcac98e2912f66a3387f057a0 + # rootGeneral: f02657442189da118ab86d745842894e + | dimensionspacepointhash | uripath | nodeaggregateidpath | nodeaggregateid | parentnodeaggregateid | precedingnodeaggregateid | succeedingnodeaggregateid | nodetypename | + | "033e5de7b423f45bb4f5a09f73af839e" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "65901ded4f068dac14ad0dce4f459b29" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "9447118dcac98e2912f66a3387f057a0" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "f02657442189da118ab86d745842894e" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "033e5de7b423f45bb4f5a09f73af839e" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "65901ded4f068dac14ad0dce4f459b29" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "9447118dcac98e2912f66a3387f057a0" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "f02657442189da118ab86d745842894e" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "033e5de7b423f45bb4f5a09f73af839e" | "elder" | "lady-eleonode-rootford/shernode-homes/elder-mc-nodeface" | "elder-mc-nodeface" | "shernode-homes" | "eldest-mc-nodeface" | "nody-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "elder" | "lady-eleonode-rootford/shernode-homes/elder-mc-nodeface" | "elder-mc-nodeface" | "shernode-homes" | "eldest-mc-nodeface" | "nody-mc-nodeface" | "Neos.Neos:Document" | + | "9447118dcac98e2912f66a3387f057a0" | "elder" | "lady-eleonode-rootford/shernode-homes/elder-mc-nodeface" | "elder-mc-nodeface" | "shernode-homes" | "eldest-mc-nodeface" | "nody-mc-nodeface" | "Neos.Neos:Document" | + | "033e5de7b423f45bb4f5a09f73af839e" | "eldest" | "lady-eleonode-rootford/shernode-homes/eldest-mc-nodeface" | "eldest-mc-nodeface" | "shernode-homes" | null | "elder-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "eldest" | "lady-eleonode-rootford/shernode-homes/eldest-mc-nodeface" | "eldest-mc-nodeface" | "shernode-homes" | null | "elder-mc-nodeface" | "Neos.Neos:Document" | + | "9447118dcac98e2912f66a3387f057a0" | "eldest" | "lady-eleonode-rootford/shernode-homes/eldest-mc-nodeface" | "eldest-mc-nodeface" | "shernode-homes" | null | "elder-mc-nodeface" | "Neos.Neos:Document" | + | "033e5de7b423f45bb4f5a09f73af839e" | "nody" | "lady-eleonode-rootford/shernode-homes/nody-mc-nodeface" | "nody-mc-nodeface" | "shernode-homes" | "elder-mc-nodeface" | "younger-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "nody" | "lady-eleonode-rootford/shernode-homes/nody-mc-nodeface" | "nody-mc-nodeface" | "shernode-homes" | "elder-mc-nodeface" | "younger-mc-nodeface" | "Neos.Neos:Document" | + | "9447118dcac98e2912f66a3387f057a0" | "nody" | "lady-eleonode-rootford/shernode-homes/nody-mc-nodeface" | "nody-mc-nodeface" | "shernode-homes" | "elder-mc-nodeface" | "younger-mc-nodeface" | "Neos.Neos:Document" | + | "033e5de7b423f45bb4f5a09f73af839e" | "younger" | "lady-eleonode-rootford/shernode-homes/younger-mc-nodeface" | "younger-mc-nodeface" | "shernode-homes" | "nody-mc-nodeface" | "youngest-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "younger" | "lady-eleonode-rootford/shernode-homes/younger-mc-nodeface" | "younger-mc-nodeface" | "shernode-homes" | "nody-mc-nodeface" | "youngest-mc-nodeface" | "Neos.Neos:Document" | + | "9447118dcac98e2912f66a3387f057a0" | "younger" | "lady-eleonode-rootford/shernode-homes/younger-mc-nodeface" | "younger-mc-nodeface" | "shernode-homes" | "nody-mc-nodeface" | "youngest-mc-nodeface" | "Neos.Neos:Document" | + | "033e5de7b423f45bb4f5a09f73af839e" | "youngest" | "lady-eleonode-rootford/shernode-homes/youngest-mc-nodeface" | "youngest-mc-nodeface" | "shernode-homes" | "younger-mc-nodeface" | null | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "youngest" | "lady-eleonode-rootford/shernode-homes/youngest-mc-nodeface" | "youngest-mc-nodeface" | "shernode-homes" | "younger-mc-nodeface" | null | "Neos.Neos:Document" | + | "9447118dcac98e2912f66a3387f057a0" | "youngest" | "lady-eleonode-rootford/shernode-homes/youngest-mc-nodeface" | "youngest-mc-nodeface" | "shernode-homes" | "younger-mc-nodeface" | null | "Neos.Neos:Document" | + + Scenario: Delete the node in a virtual specialization and then create the node in that specialization, forcing the edges to be recreated + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | example | source, spec, leafSpec | leafSpec -> spec -> source | + And using the following node types: + """yaml + 'Neos.Neos:Sites': + superTypes: + 'Neos.ContentRepository:Root': true + 'Neos.Neos:Document': + properties: + uriPathSegment: + type: string + 'Neos.Neos:Site': + superTypes: + 'Neos.Neos:Document': true + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And I am user identified by "initiating-user-identifier" + And the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | + And the graph projection is fully up to date + And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.Neos:Sites" | + And the graph projection is fully up to date + And the command CreateNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "shernode-homes" | + | nodeTypeName | "Neos.Neos:Site" | + | parentNodeAggregateId | "lady-eleonode-rootford" | + | originDimensionSpacePoint | {"example":"source"} | + And the graph projection is fully up to date + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeName | parentNodeAggregateId | succeedingSiblingNodeAggregateId | nodeTypeName | initialPropertyValues | + # Let's create our test subject... + | nody-mc-nodeface | document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "nody"} | + # ...and add some siblings to check orderings. Also, everything gets better with siblings. + | elder-mc-nodeface | elder-document | shernode-homes | nody-mc-nodeface | Neos.Neos:Document | {"uriPathSegment": "elder"} | + | eldest-mc-nodeface | eldest-document | shernode-homes | elder-mc-nodeface | Neos.Neos:Document | {"uriPathSegment": "eldest"} | + | younger-mc-nodeface | younger-document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "younger"} | + | youngest-mc-nodeface | youngest-document | shernode-homes | | Neos.Neos:Document | {"uriPathSegment": "youngest"} | + And the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example":"spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command CreateNodeVariant is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | sourceOrigin | {"example":"source"} | + | targetOrigin | {"example":"spec"} | + And the graph projection is fully up to date + + Then I expect the documenturipath table to contain exactly: + # source: 65901ded4f068dac14ad0dce4f459b29 + # spec: 9a723c057afa02982dae9d0b541739be + # leafSpec: c60c44685475d0e2e4f2b964e6158ce2 + | dimensionspacepointhash | uripath | nodeaggregateidpath | nodeaggregateid | parentnodeaggregateid | precedingnodeaggregateid | succeedingnodeaggregateid | nodetypename | + | "65901ded4f068dac14ad0dce4f459b29" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "9a723c057afa02982dae9d0b541739be" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "" | "lady-eleonode-rootford" | "lady-eleonode-rootford" | null | null | null | "Neos.Neos:Sites" | + | "65901ded4f068dac14ad0dce4f459b29" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "9a723c057afa02982dae9d0b541739be" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "" | "lady-eleonode-rootford/shernode-homes" | "shernode-homes" | "lady-eleonode-rootford" | null | null | "Neos.Neos:Site" | + | "65901ded4f068dac14ad0dce4f459b29" | "elder" | "lady-eleonode-rootford/shernode-homes/elder-mc-nodeface" | "elder-mc-nodeface" | "shernode-homes" | "eldest-mc-nodeface" | "nody-mc-nodeface" | "Neos.Neos:Document" | + | "9a723c057afa02982dae9d0b541739be" | "elder" | "lady-eleonode-rootford/shernode-homes/elder-mc-nodeface" | "elder-mc-nodeface" | "shernode-homes" | "eldest-mc-nodeface" | "nody-mc-nodeface" | "Neos.Neos:Document" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "elder" | "lady-eleonode-rootford/shernode-homes/elder-mc-nodeface" | "elder-mc-nodeface" | "shernode-homes" | "eldest-mc-nodeface" | "nody-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "eldest" | "lady-eleonode-rootford/shernode-homes/eldest-mc-nodeface" | "eldest-mc-nodeface" | "shernode-homes" | null | "elder-mc-nodeface" | "Neos.Neos:Document" | + | "9a723c057afa02982dae9d0b541739be" | "eldest" | "lady-eleonode-rootford/shernode-homes/eldest-mc-nodeface" | "eldest-mc-nodeface" | "shernode-homes" | null | "elder-mc-nodeface" | "Neos.Neos:Document" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "eldest" | "lady-eleonode-rootford/shernode-homes/eldest-mc-nodeface" | "eldest-mc-nodeface" | "shernode-homes" | null | "elder-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "nody" | "lady-eleonode-rootford/shernode-homes/nody-mc-nodeface" | "nody-mc-nodeface" | "shernode-homes" | "elder-mc-nodeface" | "younger-mc-nodeface" | "Neos.Neos:Document" | + | "9a723c057afa02982dae9d0b541739be" | "nody" | "lady-eleonode-rootford/shernode-homes/nody-mc-nodeface" | "nody-mc-nodeface" | "shernode-homes" | "elder-mc-nodeface" | "younger-mc-nodeface" | "Neos.Neos:Document" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "nody" | "lady-eleonode-rootford/shernode-homes/nody-mc-nodeface" | "nody-mc-nodeface" | "shernode-homes" | "elder-mc-nodeface" | "younger-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "younger" | "lady-eleonode-rootford/shernode-homes/younger-mc-nodeface" | "younger-mc-nodeface" | "shernode-homes" | "nody-mc-nodeface" | "youngest-mc-nodeface" | "Neos.Neos:Document" | + | "9a723c057afa02982dae9d0b541739be" | "younger" | "lady-eleonode-rootford/shernode-homes/younger-mc-nodeface" | "younger-mc-nodeface" | "shernode-homes" | "nody-mc-nodeface" | "youngest-mc-nodeface" | "Neos.Neos:Document" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "younger" | "lady-eleonode-rootford/shernode-homes/younger-mc-nodeface" | "younger-mc-nodeface" | "shernode-homes" | "nody-mc-nodeface" | "youngest-mc-nodeface" | "Neos.Neos:Document" | + | "65901ded4f068dac14ad0dce4f459b29" | "youngest" | "lady-eleonode-rootford/shernode-homes/youngest-mc-nodeface" | "youngest-mc-nodeface" | "shernode-homes" | "younger-mc-nodeface" | null | "Neos.Neos:Document" | + | "9a723c057afa02982dae9d0b541739be" | "youngest" | "lady-eleonode-rootford/shernode-homes/youngest-mc-nodeface" | "youngest-mc-nodeface" | "shernode-homes" | "younger-mc-nodeface" | null | "Neos.Neos:Document" | + | "c60c44685475d0e2e4f2b964e6158ce2" | "youngest" | "lady-eleonode-rootford/shernode-homes/youngest-mc-nodeface" | "youngest-mc-nodeface" | "shernode-homes" | "younger-mc-nodeface" | null | "Neos.Neos:Document" | From e8bbc2a12c645fe4f954ed793ee276fe240c95da Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Wed, 3 Apr 2024 23:34:57 +0200 Subject: [PATCH 012/214] pacify linter --- .../FrontendRouting/Projection/DocumentUriPathProjection.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php index 6020e083a0e..383c6a7d820 100644 --- a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php +++ b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php @@ -941,8 +941,7 @@ private function connectNodeWithSiblings( $parentNodeAggregateId, $node->getDimensionSpacePointHash(), $node->getNodeAggregateId() - ) - ); + )); } if ( $newPrecedingNode !== null From 0e45ca734d0457930fbe6ec0231f856177d71f26 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 6 Apr 2024 23:00:34 +0200 Subject: [PATCH 013/214] Enforce unique names also for root children --- .../src/Domain/Repository/ContentGraph.php | 11 ----------- .../Domain/Repository/ContentHypergraph.php | 2 -- ...AggregateWithNode_ConstraintChecks.feature | 1 - ...de_ConstraintChecks_WithDimensions.feature | 19 +++++++++++++++++++ .../Feature/Common/ConstraintChecks.php | 2 -- .../Feature/NodeCreation/NodeCreation.php | 1 - .../NodeDuplicationCommandHandler.php | 1 - .../Feature/NodeRenaming/NodeRenaming.php | 17 +++++++---------- .../ContentGraph/ContentGraphInterface.php | 1 - 9 files changed, 26 insertions(+), 29 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 1df35d06fa3..79fb14db112 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -298,19 +298,10 @@ public function findTetheredChildNodeAggregates( return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); } - /** - * @param ContentStreamId $contentStreamId - * @param NodeName $nodeName - * @param NodeAggregateId $parentNodeAggregateId - * @param OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint - * @param DimensionSpacePointSet $dimensionSpacePointsToCheck - * @return DimensionSpacePointSet - */ public function getDimensionSpacePointsOccupiedByChildNodeName( ContentStreamId $contentStreamId, NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, - OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck ): DimensionSpacePointSet { $queryBuilder = $this->createQueryBuilder() @@ -320,14 +311,12 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :parentNodeAggregateId') - ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)') ->andWhere('h.name = :nodeName') ->setParameters([ 'parentNodeAggregateId' => $parentNodeAggregateId->value, - 'parentNodeOriginDimensionSpacePointHash' => $parentNodeOriginDimensionSpacePoint->hash, 'contentStreamId' => $contentStreamId->value, 'dimensionSpacePointHashes' => $dimensionSpacePointsToCheck->getPointHashes(), 'nodeName' => $nodeName->value diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php index 49f9b7af5be..e4a1a91b167 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php @@ -273,7 +273,6 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ContentStreamId $contentStreamId, NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, - OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck ): DimensionSpacePointSet { $query = HypergraphChildQuery::create( @@ -283,7 +282,6 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ['ch.dimensionspacepoint, ch.dimensionspacepointhash'] ); $query = $query->withChildNodeName($nodeName) - ->withOriginDimensionSpacePoint($parentNodeOriginDimensionSpacePoint) ->withDimensionSpacePoints($dimensionSpacePointsToCheck); $occupiedDimensionSpacePoints = []; diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature index a14305214da..7ca3ef355a9 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature @@ -149,7 +149,6 @@ Feature: Create node aggregate with node | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | | nodeTypeName | "Neos.ContentRepository.Testing:Node" | - | originDimensionSpacePoint | {} | | parentNodeAggregateId | "lady-eleonode-rootford" | | nodeName | "document" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature index 66512883875..3fd276365f5 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature @@ -63,3 +63,22 @@ Feature: Create node aggregate with node | parentNodeAggregateId | "sir-david-nodenborough" | | originDimensionSpacePoint | {"language":"de"} | Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint" + + Scenario: Try to create a node aggregate with a root parent and a sibling already claiming the name + # root nodes are special in that they have the empty DSP as origin, wich may affect constraint checks + When the command CreateNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | nodeTypeName | "Neos.ContentRepository.Testing:Node" | + | parentNodeAggregateId | "lady-eleonode-rootford" | + | originDimensionSpacePoint | {"language":"de"} | + | nodeName | "document" | + And the graph projection is fully up to date + And the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | nodeTypeName | "Neos.ContentRepository.Testing:Node" | + | parentNodeAggregateId | "lady-eleonode-rootford" | + | originDimensionSpacePoint | {"language":"de"} | + | nodeName | "document" | + Then the last command should have thrown an exception of type "NodeNameIsAlreadyOccupied" diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 6054ce5cc7c..03cd9dbc3a9 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -548,7 +548,6 @@ protected function requireNodeNameToBeUnoccupied( ContentStreamId $contentStreamId, ?NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, - OriginDimensionSpacePoint $parentOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePoints, ContentRepository $contentRepository ): void { @@ -560,7 +559,6 @@ protected function requireNodeNameToBeUnoccupied( $contentStreamId, $nodeName, $parentNodeAggregateId, - $parentOriginDimensionSpacePoint, $dimensionSpacePoints ); if (count($dimensionSpacePointsOccupiedByChildNodeName) > 0) { diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index c2f1ff8a42a..28f68dc82fc 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -180,7 +180,6 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $contentStreamId, $command->nodeName, $command->parentNodeAggregateId, - $command->originDimensionSpacePoint, $coveredDimensionSpacePoints, $contentRepository ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php index d88df293eab..d214afdf233 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php @@ -142,7 +142,6 @@ private function handleCopyNodesRecursively( $contentStreamId, $command->targetNodeName, $command->targetParentNodeAggregateId, - $command->targetDimensionSpacePoint, $coveredDimensionSpacePoints, $contentRepository ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php index 40403f523ba..55bbe3abb00 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php @@ -42,16 +42,13 @@ private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command, $this->requireNodeAggregateToNotBeRoot($nodeAggregate, 'and Root Node Aggregates cannot be renamed'); $this->requireNodeAggregateToBeUntethered($nodeAggregate); foreach ($contentRepository->getContentGraph()->findParentNodeAggregates($contentStreamId, $command->nodeAggregateId) as $parentNodeAggregate) { - foreach ($parentNodeAggregate->occupiedDimensionSpacePoints as $occupiedParentDimensionSpacePoint) { - $this->requireNodeNameToBeUnoccupied( - $contentStreamId, - $command->newNodeName, - $parentNodeAggregate->nodeAggregateId, - $occupiedParentDimensionSpacePoint, - $parentNodeAggregate->coveredDimensionSpacePoints, - $contentRepository - ); - } + $this->requireNodeNameToBeUnoccupied( + $contentStreamId, + $command->newNodeName, + $parentNodeAggregate->nodeAggregateId, + $parentNodeAggregate->coveredDimensionSpacePoints, + $contentRepository + ); } $events = Events::with( diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 4d8d7e4a4a7..18f85d22155 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -150,7 +150,6 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ContentStreamId $contentStreamId, NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, - OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck ): DimensionSpacePointSet; From 1e48183fa8fa107ba91ae235dc92ea6151aeca38 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 6 Apr 2024 23:23:22 +0200 Subject: [PATCH 014/214] Restore origin checks, but with proper origin for root parents --- .../src/Domain/Repository/ContentGraph.php | 3 +++ .../src/Domain/Repository/ContentHypergraph.php | 2 ++ .../Classes/Feature/Common/ConstraintChecks.php | 2 ++ .../Feature/NodeCreation/NodeCreation.php | 3 +++ .../NodeDuplicationCommandHandler.php | 3 +++ .../Feature/NodeRenaming/NodeRenaming.php | 17 ++++++++++------- .../ContentGraph/ContentGraphInterface.php | 1 + 7 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 79fb14db112..4781ae1a82e 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -302,6 +302,7 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ContentStreamId $contentStreamId, NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, + OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck ): DimensionSpacePointSet { $queryBuilder = $this->createQueryBuilder() @@ -311,12 +312,14 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :parentNodeAggregateId') + ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)') ->andWhere('h.name = :nodeName') ->setParameters([ 'parentNodeAggregateId' => $parentNodeAggregateId->value, + 'parentNodeOriginDimensionSpacePointHash' => $parentNodeOriginDimensionSpacePoint->hash, 'contentStreamId' => $contentStreamId->value, 'dimensionSpacePointHashes' => $dimensionSpacePointsToCheck->getPointHashes(), 'nodeName' => $nodeName->value diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php index e4a1a91b167..49f9b7af5be 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php @@ -273,6 +273,7 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ContentStreamId $contentStreamId, NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, + OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck ): DimensionSpacePointSet { $query = HypergraphChildQuery::create( @@ -282,6 +283,7 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ['ch.dimensionspacepoint, ch.dimensionspacepointhash'] ); $query = $query->withChildNodeName($nodeName) + ->withOriginDimensionSpacePoint($parentNodeOriginDimensionSpacePoint) ->withDimensionSpacePoints($dimensionSpacePointsToCheck); $occupiedDimensionSpacePoints = []; diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 03cd9dbc3a9..6054ce5cc7c 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -548,6 +548,7 @@ protected function requireNodeNameToBeUnoccupied( ContentStreamId $contentStreamId, ?NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, + OriginDimensionSpacePoint $parentOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePoints, ContentRepository $contentRepository ): void { @@ -559,6 +560,7 @@ protected function requireNodeNameToBeUnoccupied( $contentStreamId, $nodeName, $parentNodeAggregateId, + $parentOriginDimensionSpacePoint, $dimensionSpacePoints ); if (count($dimensionSpacePointsOccupiedByChildNodeName) > 0) { diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index 28f68dc82fc..8544705466a 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -180,6 +180,9 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $contentStreamId, $command->nodeName, $command->parentNodeAggregateId, + $parentNodeAggregate->classification->isRoot() + ? DimensionSpace\OriginDimensionSpacePoint::createWithoutDimensions() + : $command->originDimensionSpacePoint, $coveredDimensionSpacePoints, $contentRepository ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php index d214afdf233..acac660dfbd 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php @@ -142,6 +142,9 @@ private function handleCopyNodesRecursively( $contentStreamId, $command->targetNodeName, $command->targetParentNodeAggregateId, + $parentNodeAggregate->classification->isRoot() + ? OriginDimensionSpacePoint::createWithoutDimensions() + : $command->targetDimensionSpacePoint, $coveredDimensionSpacePoints, $contentRepository ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php index 55bbe3abb00..40403f523ba 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php @@ -42,13 +42,16 @@ private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command, $this->requireNodeAggregateToNotBeRoot($nodeAggregate, 'and Root Node Aggregates cannot be renamed'); $this->requireNodeAggregateToBeUntethered($nodeAggregate); foreach ($contentRepository->getContentGraph()->findParentNodeAggregates($contentStreamId, $command->nodeAggregateId) as $parentNodeAggregate) { - $this->requireNodeNameToBeUnoccupied( - $contentStreamId, - $command->newNodeName, - $parentNodeAggregate->nodeAggregateId, - $parentNodeAggregate->coveredDimensionSpacePoints, - $contentRepository - ); + foreach ($parentNodeAggregate->occupiedDimensionSpacePoints as $occupiedParentDimensionSpacePoint) { + $this->requireNodeNameToBeUnoccupied( + $contentStreamId, + $command->newNodeName, + $parentNodeAggregate->nodeAggregateId, + $occupiedParentDimensionSpacePoint, + $parentNodeAggregate->coveredDimensionSpacePoints, + $contentRepository + ); + } } $events = Events::with( diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 18f85d22155..4d8d7e4a4a7 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -150,6 +150,7 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ContentStreamId $contentStreamId, NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, + OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck ): DimensionSpacePointSet; From 37efdb2b09390ad426c40cac1270981ec8ecc622 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sun, 7 Apr 2024 00:09:26 +0200 Subject: [PATCH 015/214] Extract and extend constraint check test cases --- .../01-MoveNodes_ConstraintChecks.feature | 225 ++++++++++++++++++ .../08-NodeMove/MoveNodeAggregate.feature | 155 ------------ 2 files changed, 225 insertions(+), 155 deletions(-) create mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature new file mode 100644 index 00000000000..26377560926 --- /dev/null +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature @@ -0,0 +1,225 @@ +@contentrepository @adapters=DoctrineDBAL +Feature: Move node to a new parent / within the current parent before a sibling / to the end of the sibling list + + As a user of the CR I want to move a node to a new parent / within the current parent before a sibling / to the end of the sibling list, + without affecting other nodes in the node aggregate. + + These are the base test cases for the NodeAggregateCommandHandler to block invalid commands + + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | example | general, source, spec | spec->source->general | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:Document': [] + 'Neos.ContentRepository.Testing:Content': + constraints: + nodeTypes: + '*': true + 'Neos.ContentRepository.Testing:Document': false + 'Neos.ContentRepository.Testing:DocumentWithTetheredChildNode': + childNodes: + tethered: + type: 'Neos.ContentRepository.Testing:Content' + constraints: + nodeTypes: + '*': true + 'Neos.ContentRepository.Testing:Content': false + """ + 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 the active content stream of workspace "live" and dimension space point {"example": "source"} + And the graph projection is fully up to date + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + And the graph projection is fully up to date + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | tetheredDescendantNodeAggregateIds | + | sir-david-nodenborough | {"example": "source"} | Neos.ContentRepository.Testing:DocumentWithTetheredChildNode | lady-eleonode-rootford | document | {"tethered": "nodewyn-tetherton"} | + | sir-nodeward-nodington-iii | {"example": "source"} | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | esquire | {} | + | anthony-destinode | {"example": "spec"} | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | target-document | {} | + + Scenario: Try to move a node in a non-existing workspace: + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | workspaceName | "non-existing" | + | nodeAggregateId | "sir-david-nodenborough" | + | dimensionSpacePoint | {"example": "source"} | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + + Scenario: Try to move a node in a workspace whose content stream is closed: + When the command CloseContentStream is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | dimensionSpacePoint | {"example": "source"} | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "ContentStreamIsClosed" + + Scenario: Try to move a non-existing node aggregate: + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "i-do-not-exist" | + | dimensionSpacePoint | {"example": "source"} | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateCurrentlyDoesNotExist" + + Scenario: Try to move a root node aggregate: + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | dimensionSpacePoint | {"example": "source"} | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateIsRoot" + + Scenario: Try to move a node of a tethered node aggregate: + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "nodewyn-tetherton" | + | dimensionSpacePoint | {"example": "source"} | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateIsTethered" + + Scenario: Try to move a node in a non-existing dimension space point: + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | dimensionSpacePoint | {"example": "nope"} | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "DimensionSpacePointNotFound" + + Scenario: Try to move a node in a dimension space point the aggregate does not cover + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | dimensionSpacePoint | {"example": "general"} | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint" + + Scenario: Try to move a node to a non-existing parent + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | newParentNodeAggregateId | "non-existing-parent-identifier" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateCurrentlyDoesNotExist" + + Scenario: Using the scatter strategy, try to move a node to a new, existing parent in a dimension space point the new parent does not cover + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "anthony-destinode" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePointSet" + + Scenario: Using the gatherSpecializations strategy, try to move a node to a new, existing parent in a dimension space point with a specialization the new parent does not cover + # reduce coverage of the target + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePointSet" + + Scenario: Using the gatherAll strategy, try to move a node to a new, existing parent in a dimension space point with a generalization the new parent does not cover + # increase coverage of the source + Given the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | sourceOrigin | {"example": "source"} | + | targetOrigin | {"example": "general"} | + And the graph projection is fully up to date + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherAll" | + Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePointSet" + + Scenario: Try to move a node to a parent that already has a child node of the same name + Given the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | + | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | document | + + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "lady-eleonode-rootford" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + + Scenario: Try to move a node to a parent whose node type does not allow child nodes of the node's type + Given the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | + | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | other-document | + + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "nodewyn-tetherton" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeConstraintException" + + Scenario: Try to move a node to a parent whose parent's node type does not allow grand child nodes of the node's type + Given the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | + | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Content | lady-eleonode-rootford | content | + + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "nodewyn-tetherton" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeConstraintException" + + Scenario: Try to move existing node to a non-existing preceding sibling + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "i-do-not-exist" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateCurrentlyDoesNotExist" + + Scenario: Try to move existing node to a non-existing succeeding sibling + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "i-do-not-exist" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateCurrentlyDoesNotExist" + + Scenario: Try to move a node to one of its children + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "sir-david-nodenborough" | + | newParentNodeAggregateId | "nodewyn-tetherton" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateIsDescendant" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate.feature index 5dd2d441fa8..7401c2829fa 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate.feature @@ -80,95 +80,6 @@ Feature: Move node to a new parent / within the current parent before a sibling | nodeAggregateClassification | "regular" | And the graph projection is fully up to date - Scenario: Try to move a node in a non-existing workspace: - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | workspaceName | "non-existing" | - | nodeAggregateId | "sir-david-nodenborough" | - | dimensionSpacePoint | {"market": "DE", "language": "de"} | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" - - Scenario: Try to move a node in a workspace whose content stream is closed: - When the command CloseContentStream is executed with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | nodeAggregateId | "sir-david-nodenborough" | - | dimensionSpacePoint | {"market": "DE", "language": "de"} | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "ContentStreamIsClosed" - - Scenario: Try to move a node of a non-existing node aggregate: - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | nodeAggregateId | "i-do-not-exist" | - | dimensionSpacePoint | {"market": "DE", "language": "de"} | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeAggregateCurrentlyDoesNotExist" - - Scenario: Try to move a node of a root node aggregate: - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | nodeAggregateId | "lady-eleonode-rootford" | - | dimensionSpacePoint | {"market":"DE", "language":"de"} | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeAggregateIsRoot" - - Scenario: Try to move a node of a tethered node aggregate: - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | nodeAggregateId | "nodewyn-tetherton" | - | dimensionSpacePoint | {"market": "DE", "language": "de"} | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeAggregateIsTethered" - - Scenario: Try to move a node in a non-existing dimension space point: - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | nodeAggregateId | "sir-david-nodenborough" | - | dimensionSpacePoint | {"market": "nope", "language": "neither"} | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "DimensionSpacePointNotFound" - - Scenario: Try to move a node in a dimension space point the aggregate does not cover - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | nodeAggregateId | "sir-david-nodenborough" | - | dimensionSpacePoint | {"market": "DE", "language": "fr"} | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint" - - Scenario: Try to move existing node to a non-existing parent - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | dimensionSpacePoint | {"market": "DE", "language": "de"} | - | nodeAggregateId | "sir-david-nodenborough" | - | newParentNodeAggregateId | "non-existing-parent-identifier" | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeAggregateCurrentlyDoesNotExist" - - Scenario: Try to move a node to a parent that already has a child node of the same name - Given the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nody-mc-nodeface" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"market": "DE", "language": "de"} | - | coveredDimensionSpacePoints | [{"market": "DE", "language": "de"}, {"market": "DE", "language": "gsw"}, {"market": "CH", "language": "de"}, {"market": "CH", "language": "gsw"}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeName | "document" | - | nodeAggregateClassification | "regular" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | dimensionSpacePoint | {"market": "DE", "language": "de"} | - | nodeAggregateId | "nody-mc-nodeface" | - | newParentNodeAggregateId | "lady-eleonode-rootford" | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" Scenario: Move a node that has no name Given the event NodeAggregateWithNodeWasCreated was published with payload: @@ -193,70 +104,4 @@ Feature: Move node to a new parent / within the current parent before a sibling And I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{"market":"DE","language":"de"} - Scenario: Try to move a node to a parent whose node type does not allow child nodes of the node's type - Given the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nody-mc-nodeface" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"market": "DE", "language": "de"} | - | coveredDimensionSpacePoints | [{"market": "DE", "language": "de"}, {"market": "DE", "language": "gsw"}, {"market": "CH", "language": "de"}, {"market": "CH", "language": "gsw"}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "other-document" | - | nodeAggregateClassification | "regular" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | dimensionSpacePoint | {"market": "DE", "language": "de"} | - | nodeAggregateId | "nody-mc-nodeface" | - | newParentNodeAggregateId | "nodewyn-tetherton" | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeConstraintException" - - Scenario: Try to move a node to a parent whose parent's node type does not allow grand child nodes of the node's type - Given the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nody-mc-nodeface" | - | nodeTypeName | "Neos.ContentRepository.Testing:Content" | - | originDimensionSpacePoint | {"market": "DE", "language": "de"} | - | coveredDimensionSpacePoints | [{"market": "DE", "language": "de"}, {"market": "DE", "language": "gsw"}, {"market": "CH", "language": "de"}, {"market": "CH", "language": "gsw"}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "content" | - | nodeAggregateClassification | "regular" | - And the graph projection is fully up to date - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | dimensionSpacePoint | {"market": "DE", "language": "de"} | - | nodeAggregateId | "nody-mc-nodeface" | - | newParentNodeAggregateId | "nodewyn-tetherton" | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeConstraintException" - - Scenario: Try to move existing node to a non-existing succeeding sibling - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | dimensionSpacePoint | {"market": "DE", "language": "de"} | - | nodeAggregateId | "sir-david-nodenborough" | - | newSucceedingSiblingNodeAggregateId | "i-do-not-exist" | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeAggregateCurrentlyDoesNotExist" - - Scenario: Try to move existing node to a non-existing preceding sibling - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | dimensionSpacePoint | {"market": "DE", "language": "de"} | - | nodeAggregateId | "sir-david-nodenborough" | - | newPrecedingSiblingNodeAggregateId | "i-do-not-exist" | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeAggregateCurrentlyDoesNotExist" - Scenario: Try to move a node to one of its children - When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | dimensionSpacePoint | {"market": "DE", "language": "de"} | - | nodeAggregateId | "sir-david-nodenborough" | - | newParentNodeAggregateId | "nodewyn-tetherton" | - | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeAggregateIsDescendant" From a61000358f1aabe6b8fd8da3df115e5ccbf6b6e1 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sun, 7 Apr 2024 00:31:02 +0200 Subject: [PATCH 016/214] Refactor MoveNodeAggregate - new parent test suite from events to commands --- ...odeAggregate_NewParent_Dimensions.feature} | 145 ++++++------------ .../08-NodeMove/MoveNodeAggregate.feature | 107 ------------- 2 files changed, 47 insertions(+), 205 deletions(-) rename Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/{MoveNodeAggregate_NewParent_Dimensions.feature => 02-MoveNodeAggregate_NewParent_Dimensions.feature} (69%) delete mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate.feature diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate_NewParent_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NewParent_Dimensions.feature similarity index 69% rename from Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate_NewParent_Dimensions.feature rename to Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NewParent_Dimensions.feature index e2791f32b28..16fa582f5af 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate_NewParent_Dimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NewParent_Dimensions.feature @@ -25,88 +25,26 @@ Feature: Move a node with content dimensions | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | | nodeTypeName | "Neos.ContentRepository:Root" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "document" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "anthony-destinode" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeName | "child-document-a" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "berta-destinode" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeName | "child-document-b" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "carl-destinode" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeName | "child-document-c" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-nodeward-nodington-iii" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "esquire" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nody-mc-nodeface" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "sir-nodeward-nodington-iii" | - | nodeName | "child-document-n" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "lady-abigail-nodenborough" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "document2" | - | nodeAggregateClassification | "regular" | And the graph projection is fully up to date + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | + | sir-david-nodenborough | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | document | + | anthony-destinode | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | child-document-a | + | berta-destinode | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | child-document-b | + | carl-destinode | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | child-document-c | + | sir-nodeward-nodington-iii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | esquire | + | nody-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | child-document-n | + | lady-abigail-nodenborough | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | document2 | Scenario: Move a complete node aggregate to a new parent before the first of its new siblings When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newSucceedingSiblingNodeAggregateId | "anthony-destinode" | And the graph projection is fully up to date @@ -122,18 +60,16 @@ Feature: Move a node with content dimensions | cs-identifier;carl-destinode;{"language": "mul"} | Scenario: Move a complete node aggregate to a new parent before the first of its new siblings - which does not exist in all variants - Given the event NodeAggregateWasRemoved was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "anthony-destinode" | - | affectedOccupiedDimensionSpacePoints | [] | - | affectedCoveredDimensionSpacePoints | [{"language": "gsw"}] | + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "anthony-destinode" | + | coveredDimensionSpacePoint | {"language": "gsw"} | + | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newSucceedingSiblingNodeAggregateId | "anthony-destinode" | | relationDistributionStrategy | "gatherAll" | @@ -179,18 +115,16 @@ Feature: Move a node with content dimensions | cs-identifier;carl-destinode;{"language": "mul"} | Scenario: Move a complete node aggregate to a new parent before one of its siblings - which does not exist in all variants - Given the event NodeAggregateWasRemoved was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "berta-destinode" | - | affectedOccupiedDimensionSpacePoints | [] | - | affectedCoveredDimensionSpacePoints | [{"language": "gsw"}] | + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "berta-destinode" | + | coveredDimensionSpacePoint | {"language": "gsw"} | + | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newSucceedingSiblingNodeAggregateId | "berta-destinode" | And the graph projection is fully up to date @@ -217,12 +151,11 @@ Feature: Move a node with content dimensions | cs-identifier;carl-destinode;{"language": "mul"} | Scenario: Move a complete node aggregate to a new parent after another of its new siblings - which does not exist in all variants - Given the event NodeAggregateWasRemoved was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "carl-destinode" | - | affectedOccupiedDimensionSpacePoints | [] | - | affectedCoveredDimensionSpacePoints | [{"language": "gsw"}] | + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "carl-destinode" | + | coveredDimensionSpacePoint | {"language": "gsw"} | + | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: @@ -254,12 +187,11 @@ Feature: Move a node with content dimensions And I expect this node to have no succeeding siblings Scenario: Move a complete node aggregate to a new parent after the last of its new siblings - with a predecessor which does not exist in all variants - Given the event NodeAggregateWasRemoved was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "carl-destinode" | - | affectedOccupiedDimensionSpacePoints | [] | - | affectedCoveredDimensionSpacePoints | [{"language": "gsw"}] | + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "carl-destinode" | + | coveredDimensionSpacePoint | {"language": "gsw"} | + | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: @@ -430,3 +362,20 @@ Feature: Move a node with content dimensions And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;berta-destinode;{"language": "mul"} | + + Scenario: Move a node that has no name + Given the command CreateNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface-ii" | + | nodeTypeName | "Neos.ContentRepository.Testing:Document" | + | parentNodeAggregateId | "sir-david-nodenborough" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface-ii" | + | newParentNodeAggregateId | "lady-eleonode-rootford" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} + And I expect node aggregate identifier "nody-mc-nodeface-ii" to lead to node cs-identifier;nody-mc-nodeface-ii;{"language": "mul"} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate.feature deleted file mode 100644 index 7401c2829fa..00000000000 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate.feature +++ /dev/null @@ -1,107 +0,0 @@ -@contentrepository @adapters=DoctrineDBAL -Feature: Move node to a new parent / within the current parent before a sibling / to the end of the sibling list - - As a user of the CR I want to move a node to a new parent / within the current parent before a sibling / to the end of the sibling list, - without affecting other nodes in the node aggregate. - - These are the base test cases for the NodeAggregateCommandHandler to block invalid commands - - Content Structure: - - lady-eleonode-rootford (Neos.ContentRepository:Root) - - sir-david-nodenborough (Neos.ContentRepository.Testing:DocumentWithTetheredChildNode) - - "tethered" nodewyn-tetherton (Neos.ContentRepository.Testing:Content) - - sir-nodeward-nodington-iii (Neos.ContentRepository.Testing:Document) - - Background: - Given using the following content dimensions: - | Identifier | Values | Generalizations | - | market | DE, CH | CH->DE | - | language | de, gsw, fr | gsw->de | - And using the following node types: - """yaml - 'Neos.ContentRepository.Testing:Document': [] - 'Neos.ContentRepository.Testing:Content': - constraints: - nodeTypes: - '*': true - 'Neos.ContentRepository.Testing:Document': false - 'Neos.ContentRepository.Testing:DocumentWithTetheredChildNode': - childNodes: - tethered: - type: 'Neos.ContentRepository.Testing:Content' - constraints: - nodeTypes: - '*': true - 'Neos.ContentRepository.Testing:Content': false - """ - 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 the active content stream of workspace "live" - And the graph projection is fully up to date - And the command CreateRootNodeAggregateWithNode is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-eleonode-rootford" | - | nodeTypeName | "Neos.ContentRepository:Root" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | nodeTypeName | "Neos.ContentRepository.Testing:DocumentWithTetheredChildNode" | - | originDimensionSpacePoint | {"market":"DE", "language":"de"} | - | coveredDimensionSpacePoints | [{"market":"DE", "language":"de"},{"market":"DE", "language":"gsw"},{"market":"CH", "language":"de"},{"market":"CH", "language":"gsw"}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "document" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nodewyn-tetherton" | - | nodeTypeName | "Neos.ContentRepository.Testing:Content" | - | originDimensionSpacePoint | {"market":"DE", "language":"de"} | - | coveredDimensionSpacePoints | [{"market":"DE", "language":"de"},{"market":"DE", "language":"gsw"},{"market":"CH", "language":"de"},{"market":"CH", "language":"gsw"}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeName | "tethered" | - | nodeAggregateClassification | "tethered" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-nodeward-nodington-iii" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"market":"DE", "language":"de"} | - | coveredDimensionSpacePoints | [{"market":"DE", "language":"de"},{"market":"DE", "language":"gsw"},{"market":"CH", "language":"de"},{"market":"CH", "language":"gsw"}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeName | "esquire" | - | nodeAggregateClassification | "regular" | - And the graph projection is fully up to date - - - Scenario: Move a node that has no name - Given the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nody-mc-nodeface" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"market": "DE", "language": "de"} | - | coveredDimensionSpacePoints | [{"market": "DE", "language": "de"}, {"market": "DE", "language": "gsw"}, {"market": "CH", "language": "de"}, {"market": "CH", "language": "gsw"}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeAggregateClassification | "regular" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | dimensionSpacePoint | {"market": "DE", "language": "de"} | - | nodeAggregateId | "nody-mc-nodeface" | - | newParentNodeAggregateId | "lady-eleonode-rootford" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"market": "DE", "language": "de"} - And I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{"market":"DE","language":"de"} - - - From 63e6813e0ab034ce89300624c59b051c9eb2ed97 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 8 Apr 2024 00:44:33 +0200 Subject: [PATCH 017/214] WIP Add extensive (failing) test suite for MoveNode without a new parent --- ...deAggregate_NoNewParent_Dimensions.feature | 2224 +++++++++++++++++ ...odeAggregate_NewParent_Dimensions.feature} | 0 ...deAggregate_NoNewParent_Dimensions.feature | 573 ----- 3 files changed, 2224 insertions(+), 573 deletions(-) create mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NoNewParent_Dimensions.feature rename Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/{02-MoveNodeAggregate_NewParent_Dimensions.feature => 03-MoveNodeAggregate_NewParent_Dimensions.feature} (100%) delete mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate_NoNewParent_Dimensions.feature diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NoNewParent_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NoNewParent_Dimensions.feature new file mode 100644 index 00000000000..bf97db53f9a --- /dev/null +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NoNewParent_Dimensions.feature @@ -0,0 +1,2224 @@ +@contentrepository @adapters=DoctrineDBAL +Feature: Move a node with content dimensions + + As a user of the CR I want to move a node + - before the first of its siblings + - between two of its siblings + - after the last of its siblings + + These are the test cases for moving nodes with content dimensions being involved, which is a lot more fun! + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | example | general, source, peer, spec | spec->source->general, peer->general | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:Document': [] + """ + 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 graph projection is fully up to date + And I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | + | sir-david-nodenborough | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | document | + | eldest-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | child-document-a | + | elder-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | child-document-b | + | nody-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | child-document-n | + | younger-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | child-document-c | + | youngest-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | child-document-d | + + # Test cases for the gatherAll strategy + + Scenario: Move a complete node aggregate before the first of its siblings + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate before the first of its siblings, which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate before a siblings which is partially the first + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate before one of its siblings, which is not the first + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate before one of its siblings, which is not the first and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "elder-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate before one of its siblings, which is the last and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "youngest-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate after the last of its siblings + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + Scenario: Move a complete node aggregate after the last of its siblings, which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + Scenario: Move a complete node aggregate after one of its siblings, which is partially the last + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate after one of its siblings, which is not the last + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate after one of its siblings, which is not the last and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "younger-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate after one of its siblings, which is the first and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + # Test cases for the gatherSpecializations strategy + + Scenario: Move a node and its specialization variants before the first of its siblings + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a node and its specialization variants before the first of its siblings, which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a node and its specialization variants before a siblings which is partially the first + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a node and its specialization variants before one of its siblings, which is not the first + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a node and its specialization variants before one of its siblings, which is not the first and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "elder-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a node and its specialization variants before one of its siblings, which is the last and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "youngest-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a node and its specialization variants after the last of its siblings + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a node and its specialization variants after the last of its siblings, which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a node and its specialization variants after one of its siblings, which is partially the last + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a node and its specialization variants after one of its siblings, which is not the last + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a node and its specialization variants after one of its siblings, which is not the last and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "younger-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a node and its specialization variants after one of its siblings, which is the first and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + # Test cases for the scatter strategy + + Scenario: Move a single node before the first of its siblings + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a single node before the first of its siblings, which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a single node before a siblings which is partially the first + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a single node before one of its siblings, which is not the first + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a single node before one of its siblings, which is not the first and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "elder-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a single node before one of its siblings, which is the last and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "youngest-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a single node after the last of its siblings + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a single node after the last of its siblings, which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a single node after one of its siblings, which is partially the last + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a single node after one of its siblings, which is not the last + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a single node after one of its siblings, which is not the last and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "younger-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a single node after one of its siblings, which is the first and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no succeeding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NewParent_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature similarity index 100% rename from Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NewParent_Dimensions.feature rename to Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate_NoNewParent_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate_NoNewParent_Dimensions.feature deleted file mode 100644 index fa4d0a3467f..00000000000 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregate_NoNewParent_Dimensions.feature +++ /dev/null @@ -1,573 +0,0 @@ -@contentrepository @adapters=DoctrineDBAL -Feature: Move a node with content dimensions - - As a user of the CR I want to move a node - - before the first of its siblings - - between two of its siblings - - after the last of its siblings - - These are the test cases for moving nodes with content dimensions being involved, which is a lot more fun! - - Background: - Given using the following content dimensions: - | Identifier | Values | Generalizations | - | language | mul, de, en, gsw | gsw->de->mul, en->mul | - And using the following node types: - """yaml - 'Neos.ContentRepository.Testing:Document': [] - """ - 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 graph projection is fully up to date - And I am in the active content stream of workspace "live" - And the command CreateRootNodeAggregateWithNode is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-eleonode-rootford" | - | nodeTypeName | "Neos.ContentRepository:Root" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "document" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "anthony-destinode" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeName | "child-document-a" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "berta-destinode" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeName | "child-document-b" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nody-mc-nodeface" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeName | "child-document-n" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "carl-destinode" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeName | "child-document-c" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-nodeward-nodington-iii" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language": "mul"} | - | coveredDimensionSpacePoints | [{"language": "mul"}, {"language": "de"}, {"language": "en"}, {"language": "gsw"}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "esquire" | - | nodeAggregateClassification | "regular" | - And the graph projection is fully up to date - - Scenario: Move a complete node aggregate before the first of its siblings - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | "anthony-destinode" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have no preceding siblings - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | - - Scenario: Move a complete node aggregate before the first of its siblings - which does not exist in all variants - Given the event NodeAggregateWasRemoved was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "anthony-destinode" | - | affectedOccupiedDimensionSpacePoints | [] | - | affectedCoveredDimensionSpacePoints | [{"language": "gsw"}] | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | "anthony-destinode" | - | relationDistributionStrategy | "gatherAll" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have no preceding siblings - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | - - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have no preceding siblings - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | - - Scenario: Move a complete node aggregate before another of its siblings - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | "berta-destinode" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | - - Scenario: Move a complete node aggregate before another of its siblings - which does not exist in all variants - Given the event NodeAggregateWasRemoved was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "berta-destinode" | - | affectedOccupiedDimensionSpacePoints | [] | - | affectedCoveredDimensionSpacePoints | [{"language": "gsw"}] | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | "berta-destinode" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | - - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - - Scenario: Move a complete node aggregate after another of its siblings - which does not exist in all variants - Given the event NodeAggregateWasRemoved was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "carl-destinode" | - | affectedOccupiedDimensionSpacePoints | [] | - | affectedCoveredDimensionSpacePoints | [{"language": "gsw"}] | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | - | newParentNodeAggregateId | null | - | newPrecedingSiblingNodeAggregateId | "berta-destinode" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have no succeeding siblings - - Scenario: Move a complete node aggregate after the last of its siblings - with a predecessor which does not exist in all variants - Given the event NodeAggregateWasRemoved was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "carl-destinode" | - | affectedOccupiedDimensionSpacePoints | [] | - | affectedCoveredDimensionSpacePoints | [{"language": "gsw"}] | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have no succeeding siblings - - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have no succeeding siblings - - Scenario: Move a single node before the first of its siblings - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | - | newSucceedingSiblingNodeAggregateId | "anthony-destinode" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have no preceding siblings - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | - - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - - Scenario: Move a single node between two of its siblings - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | - | newSucceedingSiblingNodeAggregateId | "berta-destinode" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | - - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - - Scenario: Move a single node to the end of its siblings - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have no succeeding siblings - - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - - Scenario: Move a node and its specializations before the first of its siblings - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "de"} | - | newSucceedingSiblingNodeAggregateId | "anthony-destinode" | - | relationDistributionStrategy | "gatherSpecializations" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have no preceding siblings - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | - - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have no preceding siblings - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | - - Scenario: Move a node and its specializations between two of its siblings - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "de"} | - | newSucceedingSiblingNodeAggregateId | "berta-destinode" | - | relationDistributionStrategy | "gatherSpecializations" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | - - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | - - Scenario: Move a node and its specializations to the end of its siblings - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "de"} | - | relationDistributionStrategy | "gatherSpecializations" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have no succeeding siblings - - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have no succeeding siblings - - Scenario: Trigger position update in DBAL graph - Given I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - # distance i to x: 128 - Given the following CreateNodeAggregateWithNode commands are executed: - | nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | - | lady-nodette-nodington-i | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-i | - | lady-nodette-nodington-x | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-x | - | lady-nodette-nodington-ix | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-ix | - | lady-nodette-nodington-viii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-viii | - | lady-nodette-nodington-vii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-vii | - | lady-nodette-nodington-vi | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-vi | - | lady-nodette-nodington-v | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-v | - | lady-nodette-nodington-iv | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-iv | - | lady-nodette-nodington-iii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-iii | - | lady-nodette-nodington-ii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-ii | - # distance ii to x: 64 - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-nodette-nodington-ii" | - | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | - And the graph projection is fully up to date - # distance iii to x: 32 - And the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-nodette-nodington-iii" | - | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | - And the graph projection is fully up to date - # distance iv to x: 16 - And the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-nodette-nodington-iv" | - | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | - And the graph projection is fully up to date - # distance v to x: 8 - And the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-nodette-nodington-v" | - | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | - And the graph projection is fully up to date - # distance vi to x: 4 - And the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-nodette-nodington-vi" | - | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | - And the graph projection is fully up to date - # distance vii to x: 2 - And the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-nodette-nodington-vii" | - | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | - And the graph projection is fully up to date - # distance viii to x: 1 -> reorder -> 128 - And the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-nodette-nodington-viii" | - | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | - And the graph projection is fully up to date - # distance ix to x: 64 after reorder - And the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-nodette-nodington-ix" | - | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | - And the graph projection is fully up to date - - Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} - And I expect this node to have the following child nodes: - | Name | NodeDiscriminator | - | document | cs-identifier;sir-david-nodenborough;{"language": "mul"} | - | esquire | cs-identifier;sir-nodeward-nodington-iii;{"language": "mul"} | - | nodington-i | cs-identifier;lady-nodette-nodington-i;{"language": "mul"} | - | nodington-ii | cs-identifier;lady-nodette-nodington-ii;{"language": "mul"} | - | nodington-iii | cs-identifier;lady-nodette-nodington-iii;{"language": "mul"} | - | nodington-iv | cs-identifier;lady-nodette-nodington-iv;{"language": "mul"} | - | nodington-v | cs-identifier;lady-nodette-nodington-v;{"language": "mul"} | - | nodington-vi | cs-identifier;lady-nodette-nodington-vi;{"language": "mul"} | - | nodington-vii | cs-identifier;lady-nodette-nodington-vii;{"language": "mul"} | - | nodington-viii | cs-identifier;lady-nodette-nodington-viii;{"language": "mul"} | - | nodington-ix | cs-identifier;lady-nodette-nodington-ix;{"language": "mul"} | - | nodington-x | cs-identifier;lady-nodette-nodington-x;{"language": "mul"} | - - From 6ca7fc2b3e5178a11e13fc0e1b9aa1bee06cac9b Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 8 Apr 2024 00:44:57 +0200 Subject: [PATCH 018/214] Move SiblingPositions test cases to DoctrineDbal adapter --- .../Projection/SiblingPositions.feature | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/SiblingPositions.feature diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/SiblingPositions.feature b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/SiblingPositions.feature new file mode 100644 index 00000000000..f8a1f8d864f --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/SiblingPositions.feature @@ -0,0 +1,111 @@ +@contentrepository +Feature: Sibling positions are properly resolved + + In the general DBAL adapter, hierarchy relations are sorted by an integer field. It defaults to a distance of 128, + which is reduced each time a node is inserted between two siblings. Once the number becomes uneven, the siblings positions are recalculated. + These are the test cases for this behavior. + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | example | general, source, peer, spec | spec->source->general, peer->general | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:Document': [] + """ + 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 graph projection is fully up to date + And I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | + | sir-nodeward-nodington-iii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | esquire | + | lady-nodette-nodington-i | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-i | + | lady-nodette-nodington-x | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-x | + | lady-nodette-nodington-ix | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-ix | + | lady-nodette-nodington-viii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-viii | + | lady-nodette-nodington-vii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-vii | + | lady-nodette-nodington-vi | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-vi | + | lady-nodette-nodington-v | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-v | + | lady-nodette-nodington-iv | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-iv | + | lady-nodette-nodington-iii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-iii | + | lady-nodette-nodington-ii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | nodington-ii | + + + Scenario: Trigger position update in DBAL graph + Given I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + # distance i to x: 128 + # distance ii to x: 64 + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-nodette-nodington-ii" | + | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | + And the graph projection is fully up to date + # distance iii to x: 32 + And the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-nodette-nodington-iii" | + | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | + And the graph projection is fully up to date + # distance iv to x: 16 + And the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-nodette-nodington-iv" | + | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | + And the graph projection is fully up to date + # distance v to x: 8 + And the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-nodette-nodington-v" | + | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | + And the graph projection is fully up to date + # distance vi to x: 4 + And the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-nodette-nodington-vi" | + | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | + And the graph projection is fully up to date + # distance vii to x: 2 + And the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-nodette-nodington-vii" | + | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | + And the graph projection is fully up to date + # distance viii to x: 1 -> reorder -> 128 + And the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-nodette-nodington-viii" | + | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | + And the graph projection is fully up to date + # distance ix to x: 64 after reorder + And the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-nodette-nodington-ix" | + | newSucceedingSiblingNodeAggregateId | "lady-nodette-nodington-x" | + And the graph projection is fully up to date + + Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} + And I expect this node to have the following child nodes: + | Name | NodeDiscriminator | + | document | cs-identifier;sir-david-nodenborough;{"example": "general"} | + | esquire | cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} | + | nodington-i | cs-identifier;lady-nodette-nodington-i;{"example": "general"} | + | nodington-ii | cs-identifier;lady-nodette-nodington-ii;{"example": "general"} | + | nodington-iii | cs-identifier;lady-nodette-nodington-iii;{"example": "general"} | + | nodington-iv | cs-identifier;lady-nodette-nodington-iv;{"example": "general"} | + | nodington-v | cs-identifier;lady-nodette-nodington-v;{"example": "general"} | + | nodington-vi | cs-identifier;lady-nodette-nodington-vi;{"example": "general"} | + | nodington-vii | cs-identifier;lady-nodette-nodington-vii;{"example": "general"} | + | nodington-viii | cs-identifier;lady-nodette-nodington-viii;{"example": "general"} | + | nodington-ix | cs-identifier;lady-nodette-nodington-ix;{"example": "general"} | + | nodington-x | cs-identifier;lady-nodette-nodington-x;{"example": "general"} | From eb44fa38a6dc51ae6b4787df19262a48b59021b3 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Wed, 10 Apr 2024 00:14:55 +0200 Subject: [PATCH 019/214] WIP: Introduce comprehensive test suite for moving to a new parent ... without prior distribution of siblings among different parents --- ...NodeAggregate_NewParent_Dimensions.feature | 2344 +++++++++++++++-- ...egate_ImplicitNewParent_Dimensions.feature | 5 + 2 files changed, 2167 insertions(+), 182 deletions(-) create mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ImplicitNewParent_Dimensions.feature diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature index 16fa582f5af..4595c23e481 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature @@ -10,8 +10,8 @@ Feature: Move a node with content dimensions Background: Given using the following content dimensions: - | Identifier | Values | Generalizations | - | language | mul, de, en, gsw | gsw->de->mul, en->mul | + | Identifier | Values | Generalizations | + | language | general, source, peer, gsw | gsw->source->general, peer->general | And using the following node types: """yaml 'Neos.ContentRepository.Testing:Document': [] @@ -25,100 +25,267 @@ Feature: Move a node with content dimensions | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} + And I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | | nodeTypeName | "Neos.ContentRepository:Root" | And the graph projection is fully up to date And the following CreateNodeAggregateWithNode commands are executed: - | nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | - | sir-david-nodenborough | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | document | - | anthony-destinode | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | child-document-a | - | berta-destinode | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | child-document-b | - | carl-destinode | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | child-document-c | - | sir-nodeward-nodington-iii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | esquire | - | nody-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | child-document-n | - | lady-abigail-nodenborough | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | document2 | + | nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | + | sir-david-nodenborough | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | parent-document | + | eldest-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | eldest-document | + | elder-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | elder-document | + | younger-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | younger-document | + | youngest-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | youngest-document | + | sir-nodeward-nodington-iii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | esquire | + | source-elder-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | elder-document | + | nody-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | document | + | source-younger-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | younger-document | + | bustling-mc-nodeface | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | bustling-document | + + # Test cases for the gatherAll strategy Scenario: Move a complete node aggregate to a new parent before the first of its new siblings When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | | newParentNodeAggregateId | "sir-david-nodenborough" | - | newSucceedingSiblingNodeAggregateId | "anthony-destinode" | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | Scenario: Move a complete node aggregate to a new parent before the first of its new siblings - which does not exist in all variants Given the command RemoveNodeAggregate is executed with payload: | Key | Value | - | nodeAggregateId | "anthony-destinode" | - | coveredDimensionSpacePoint | {"language": "gsw"} | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | | newParentNodeAggregateId | "sir-david-nodenborough" | - | newSucceedingSiblingNodeAggregateId | "anthony-destinode" | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before a siblings which is partially the first + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent before one of its new siblings + Scenario: Move a complete node aggregate to a new parent before one of its new siblings, which is not the first When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | + | dimensionSpacePoint | {"example": "general"} | | newParentNodeAggregateId | "sir-david-nodenborough" | - | newSucceedingSiblingNodeAggregateId | "berta-destinode" | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent before one of its siblings - which does not exist in all variants + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before one of its siblings, which is not the first and does not exist in all variants Given the command RemoveNodeAggregate is executed with payload: | Key | Value | - | nodeAggregateId | "berta-destinode" | - | coveredDimensionSpacePoint | {"language": "gsw"} | + | nodeAggregateId | "elder-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date @@ -126,242 +293,2055 @@ Feature: Move a node with content dimensions | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | | newParentNodeAggregateId | "sir-david-nodenborough" | - | newSucceedingSiblingNodeAggregateId | "berta-destinode" | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent after another of its new siblings - which does not exist in all variants + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before one of its siblings, which is the last and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "youngest-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after the last of its siblings + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + Scenario: Move a complete node aggregate to a new parent after the last of its siblings, which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is partially the last + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is not the last + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is not the last and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "younger-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is the first and does not exist in all variants Given the command RemoveNodeAggregate is executed with payload: | Key | Value | - | nodeAggregateId | "carl-destinode" | - | coveredDimensionSpacePoint | {"language": "gsw"} | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | + | dimensionSpacePoint | {"example": "source"} | | newParentNodeAggregateId | "sir-david-nodenborough" | - | newPrecedingSiblingNodeAggregateId | "berta-destinode" | + | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | - And I expect this node to have no succeeding siblings + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent after the last of its new siblings - with a predecessor which does not exist in all variants + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + # Test cases for the gatherSpecializations strategy + + Scenario: Move a complete node aggregate to a new parent before the first of its new siblings + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before the first of its new siblings - which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before a siblings which is partially the first Given the command RemoveNodeAggregate is executed with payload: | Key | Value | - | nodeAggregateId | "carl-destinode" | - | coveredDimensionSpacePoint | {"language": "gsw"} | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before one of its new siblings, which is not the first + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "general"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before one of its siblings, which is not the first and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "elder-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before one of its siblings, which is the last and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "youngest-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after the last of its siblings + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after the last of its siblings, which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is partially the last + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is not the last + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is not the last and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "younger-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is the first and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + # Test cases for the scatter strategy + + Scenario: Move a complete node aggregate to a new parent before the first of its new siblings + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before the first of its new siblings - which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before a siblings which is partially the first + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before one of its new siblings, which is not the first + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "general"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before one of its siblings, which is not the first and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "elder-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent before one of its siblings, which is the last and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "youngest-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after the last of its siblings + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after the last of its siblings, which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is partially the last + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "youngest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is not the last + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is not the last and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "younger-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is the first and does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + # Legacy test cases + + Scenario: Move a complete node aggregate to a new parent after another of its new siblings - which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "younger-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "general"} | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "elder-mc-nodeface" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings + + Scenario: Move a complete node aggregate to a new parent after the last of its new siblings - with a predecessor which does not exist in all variants + Given the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "younger-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "general"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newSucceedingSiblingNodeAggregateId | null | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings Scenario: Move a single node in a node aggregate to a new parent after the last of its new siblings When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "de"} | + | dimensionSpacePoint | {"example": "source"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newSucceedingSiblingNodeAggregateId | null | | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have no preceding siblings And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings Scenario: Move a node and its specializations in a node aggregate to a new parent after the last of its new siblings When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "de"} | + | dimensionSpacePoint | {"example": "source"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newSucceedingSiblingNodeAggregateId | null | | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have no preceding siblings And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;anthony-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings Scenario: Move a complete node aggregate to a new parent between siblings with different parents in other variants Given the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "berta-destinode" | - | dimensionSpacePoint | {"language": "gsw"} | - | newParentNodeAggregateId | "lady-abigail-nodenborough" | - | newSucceedingSiblingNodeAggregateId | null | - | relationDistributionStrategy | "scatter" | + | Key | Value | + | nodeAggregateId | "elder-mc-nodeface" | + | dimensionSpacePoint | {"example": "spec"} | + | newParentNodeAggregateId | "bustling-mc-nodeface" | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | + | dimensionSpacePoint | {"example": "general"} | | newParentNodeAggregateId | "sir-david-nodenborough" | - | newPrecedingSiblingNodeAggregateId | "anthony-destinode" | - | newSucceedingSiblingNodeAggregateId | "berta-destinode" | + | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | # An explicitly given parent node aggregate identifier should overrule given sibling identifiers - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;carl-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | Scenario: Move a complete node aggregate between siblings with different parents in other variants (without explicit new parent) Given the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "berta-destinode" | - | dimensionSpacePoint | {"language": "gsw"} | - | newParentNodeAggregateId | "lady-abigail-nodenborough" | - | newSucceedingSiblingNodeAggregateId | null | - | relationDistributionStrategy | "scatter" | + | Key | Value | + | nodeAggregateId | "elder-mc-nodeface" | + | dimensionSpacePoint | {"example": "spec"} | + | newParentNodeAggregateId | "bustling-mc-nodeface" | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"language": "mul"} | - | newPrecedingSiblingNodeAggregateId | "anthony-destinode" | - | newSucceedingSiblingNodeAggregateId | "berta-destinode" | - | relationDistributionStrategy | "gatherAll" | + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "general"} | + | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;anthony-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | - | cs-identifier;carl-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "document2/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"language": "mul"} - And I expect this node to be a child of node cs-identifier;lady-abigail-nodenborough;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "bustling-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;bustling-mc-nodeface;{"example": "general"} And I expect this node to have no preceding siblings And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;berta-destinode;{"language": "mul"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | Scenario: Move a node that has no name Given the command CreateNodeAggregateWithNode is executed with payload: @@ -377,5 +2357,5 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "lady-eleonode-rootford" | | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} - And I expect node aggregate identifier "nody-mc-nodeface-ii" to lead to node cs-identifier;nody-mc-nodeface-ii;{"language": "mul"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I expect node aggregate identifier "nody-mc-nodeface-ii" to lead to node cs-identifier;nody-mc-nodeface-ii;{"example": "general"} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ImplicitNewParent_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ImplicitNewParent_Dimensions.feature new file mode 100644 index 00000000000..1ea51857ef9 --- /dev/null +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ImplicitNewParent_Dimensions.feature @@ -0,0 +1,5 @@ +@contentrepository @adapters=DoctrineDBAL +Feature: Move a node with content dimensions + + As a user of the CR, when I move a node to new siblings and they belong to another parent (completely or in variants), + I expect them to be ignored. From 3ee4a29680e57b23d94037c43bba1c6e69686b34 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Thu, 11 Apr 2024 00:02:40 +0200 Subject: [PATCH 020/214] 4742 - Remove obsolete includingSelf switch --- .../Projection/DocumentUriPathProjection.php | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php index 383c6a7d820..7d4fabb2aeb 100644 --- a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php +++ b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php @@ -449,7 +449,7 @@ private function copyVariants( ->withoutSiblings(); $this->insertNode($targetNode->toArray()); - $this->connectNodeWithSiblings($targetNode, $targetNode->getParentNodeAggregateId(), $interdimensionalSibling->nodeAggregateId, false); + $this->connectNodeWithSiblings($targetNode, $targetNode->getParentNodeAggregateId(), $interdimensionalSibling->nodeAggregateId); } } @@ -658,7 +658,7 @@ private function moveNode( ): void { $this->disconnectNodeFromSiblings($node); - $this->connectNodeWithSiblings($node, $newParentNodeAggregateId, $newSucceedingNodeAggregateId, true); + $this->connectNodeWithSiblings($node, $newParentNodeAggregateId, $newSucceedingNodeAggregateId); if ($newParentNodeAggregateId->equals($node->getParentNodeAggregateId())) { return; @@ -909,14 +909,10 @@ private function disconnectNodeFromSiblings(DocumentNodeInfo $node): void } } - /** - * @param bool $includingSelf whether the node itself is to be considered its own sibling. true for moving, false for variation - */ private function connectNodeWithSiblings( DocumentNodeInfo $node, NodeAggregateId $parentNodeAggregateId, ?NodeAggregateId $newSucceedingNodeAggregateId, - bool $includingSelf, ): void { if ($newSucceedingNodeAggregateId !== null) { $newPrecedingNode = $this->tryGetNode(fn () => $this->getState()->getPrecedingNode( @@ -932,20 +928,15 @@ private function connectNodeWithSiblings( ['precedingNodeAggregateId' => $node->getNodeAggregateId()->value] ); } else { - $newPrecedingNode = $this->tryGetNode(fn () => $includingSelf - ? $this->getState()->getLastChildNode( - $parentNodeAggregateId, - $node->getDimensionSpacePointHash(), - ) - : $this->getState()->getLastChildNodeNotBeing( - $parentNodeAggregateId, - $node->getDimensionSpacePointHash(), - $node->getNodeAggregateId() - )); + $newPrecedingNode = $this->tryGetNode(fn () => $this->getState()->getLastChildNodeNotBeing( + $parentNodeAggregateId, + $node->getDimensionSpacePointHash(), + $node->getNodeAggregateId() + )); } if ( $newPrecedingNode !== null - && ($includingSelf || !$newPrecedingNode->getNodeAggregateId()->equals($node->getNodeAggregateId())) + && !$newPrecedingNode->getNodeAggregateId()->equals($node->getNodeAggregateId()) ) { $this->updateNode( $newPrecedingNode, @@ -958,7 +949,7 @@ private function connectNodeWithSiblings( 'succeedingNodeAggregateId' => $newSucceedingNodeAggregateId?->value, ]; if ( - $includingSelf || !$newPrecedingNode?->getNodeAggregateId()->equals($node->getNodeAggregateId()) + !$newPrecedingNode?->getNodeAggregateId()->equals($node->getNodeAggregateId()) ) { $updatedNodeData['precedingNodeAggregateId'] = $newPrecedingNode?->getNodeAggregateId()->value; } From c7e2d792bc31a068db5f5e46056e16cc2f777762 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 13 Apr 2024 23:23:18 +0200 Subject: [PATCH 021/214] Introduce more comprehensive MoveNode test suite --- .../src/Domain/Projection/NodeRecord.php | 1 - .../01-MoveNodes_ConstraintChecks.feature | 106 +- ...NodeAggregate_NewParent_Dimensions.feature | 215 +- ...egate_ImplicitNewParent_Dimensions.feature | 5 - ...oveNodeAggregate_ScatteredChildren.feature | 271 ++ .../05-MoveNodeAggregate_SubtreeTags.feature | 2790 +++++++++++++++++ .../06-AdditionalConstraintChecks.feature | 6 + ...oveNodeAggregateWithoutDimensions.feature} | 0 ...eringDisableStateWithoutDimensions.feature | 343 -- .../Projection/ContentGraph/NodeTags.php | 4 +- .../Features/Bootstrap/ProjectedNodeTrait.php | 22 + 11 files changed, 3192 insertions(+), 571 deletions(-) delete mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ImplicitNewParent_Dimensions.feature create mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ScatteredChildren.feature create mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature create mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature rename Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/{MoveNodeAggregateWithoutDimensions.feature => 07-MoveNodeAggregateWithoutDimensions.feature} (100%) delete mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregateConsideringDisableStateWithoutDimensions.feature diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php index fbc75314807..f9bfb9f6d7d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php @@ -15,7 +15,6 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Types\Types; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature index 26377560926..34dabe37aa4 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature @@ -44,10 +44,12 @@ Feature: Move node to a new parent / within the current parent before a sibling | nodeTypeName | "Neos.ContentRepository:Root" | And the graph projection is fully up to date And the following CreateNodeAggregateWithNode commands are executed: - | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | tetheredDescendantNodeAggregateIds | - | sir-david-nodenborough | {"example": "source"} | Neos.ContentRepository.Testing:DocumentWithTetheredChildNode | lady-eleonode-rootford | document | {"tethered": "nodewyn-tetherton"} | - | sir-nodeward-nodington-iii | {"example": "source"} | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | esquire | {} | - | anthony-destinode | {"example": "spec"} | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | target-document | {} | + | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | tetheredDescendantNodeAggregateIds | + | sir-david-nodenborough | {"example": "source"} | Neos.ContentRepository.Testing:DocumentWithTetheredChildNode | lady-eleonode-rootford | document | {"tethered": "nodewyn-tetherton"} | + | nodimus-mediocre | {"example": "source"} | Neos.ContentRepository.Testing:Document | nodewyn-tetherton | grandchild-document | {} | + | sir-nodeward-nodington-iii | {"example": "source"} | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | esquire | {} | + | anthony-destinode | {"example": "spec"} | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | target-document | {} | + | lady-abigail-nodenborough | {"example": "spec"} | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | child-target-document | {} | Scenario: Try to move a node in a non-existing workspace: When the command MoveNodeAggregate is executed with payload and exceptions are caught: @@ -158,7 +160,7 @@ Feature: Move node to a new parent / within the current parent before a sibling | relationDistributionStrategy | "gatherAll" | Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePointSet" - Scenario: Try to move a node to a parent that already has a child node of the same name + Scenario: Using the scatter strategy, try to move a node to a parent that already has a child node of the same name Given the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | document | @@ -171,6 +173,46 @@ Feature: Move node to a new parent / within the current parent before a sibling | relationDistributionStrategy | "scatter" | Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + Scenario: Using the gatherSpecializations strategy, try to move a node to a parent that already has a child node of the same name in a specialization + Given the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | + | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | target-document | + + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "lady-eleonode-rootford" | + | relationDistributionStrategy | "gatherSpecializations" | + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + + Scenario: Using the gatherAll strategy, try to move a node to a parent that already has a child node of the same name in a generalization + Given the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | + | rival-destinode | {"example": "general"} | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | target-document | + | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | target-document | + # Remove the node with the conflicting name in all variants except the generalization + And the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "rival-destinode" | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + And the command RemoveNodeAggregate is executed with payload: + | Key | Value | + | coveredDimensionSpacePoint | {"example": "peer"} | + | nodeAggregateId | "rival-destinode" | + | nodeVariantSelectionStrategy | "allSpecializations" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherAll" | + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + Scenario: Try to move a node to a parent whose node type does not allow child nodes of the node's type Given the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | @@ -184,7 +226,7 @@ Feature: Move node to a new parent / within the current parent before a sibling | relationDistributionStrategy | "scatter" | Then the last command should have thrown an exception of type "NodeConstraintException" - Scenario: Try to move a node to a parent whose parent's node type does not allow grand child nodes of the node's type + Scenario: Using the scatter strategy, try to move a node to a parent whose parent's node type does not allow grand child nodes of the node's type Given the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Content | lady-eleonode-rootford | content | @@ -197,6 +239,11 @@ Feature: Move node to a new parent / within the current parent before a sibling | relationDistributionStrategy | "scatter" | Then the last command should have thrown an exception of type "NodeConstraintException" + # Hint: One might come to the conclusion that we also have to check the other strategies. + # The cases would be that the new parent is partially moved to a new grandparent which then imposes different constraints. + # Yet these grandparent constraints are only imposed on the children of their tethered children, but the latter by definition cannot be moved. + # Thus the preconditions for these test cases cannot be established because the needed commands would fail. + Scenario: Try to move existing node to a non-existing preceding sibling When the command MoveNodeAggregate is executed with payload and exceptions are caught: | Key | Value | @@ -206,6 +253,25 @@ Feature: Move node to a new parent / within the current parent before a sibling | relationDistributionStrategy | "scatter" | Then the last command should have thrown an exception of type "NodeAggregateCurrentlyDoesNotExist" + Scenario: Try to move existing node after a node which is not a sibling + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "sir-david-nodenborough" | + | newPrecedingSiblingNodeAggregateId | "lady-abigail-nodenborough" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateIsNoSibling" + + Scenario: Try to move existing node after a node which is not a child of the new parent + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "sir-david-nodenborough" | + | newParentNodeAggregateId | "anthony-destinode" | + | newPrecedingSiblingNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateIsNoSibling" + Scenario: Try to move existing node to a non-existing succeeding sibling When the command MoveNodeAggregate is executed with payload and exceptions are caught: | Key | Value | @@ -215,6 +281,25 @@ Feature: Move node to a new parent / within the current parent before a sibling | relationDistributionStrategy | "scatter" | Then the last command should have thrown an exception of type "NodeAggregateCurrentlyDoesNotExist" + Scenario: Try to move existing node before a node which is not a sibling + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "sir-david-nodenborough" | + | newSucceedingSiblingNodeAggregateId | "lady-abigail-nodenborough" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateIsNoSibling" + + Scenario: Try to move existing node before a node which is not a child of the new parent + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "sir-david-nodenborough" | + | newParentNodeAggregateId | "anthony-destinode" | + | newSucceedingSiblingNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateIsNoSibling" + Scenario: Try to move a node to one of its children When the command MoveNodeAggregate is executed with payload and exceptions are caught: | Key | Value | @@ -223,3 +308,12 @@ Feature: Move node to a new parent / within the current parent before a sibling | newParentNodeAggregateId | "nodewyn-tetherton" | | relationDistributionStrategy | "scatter" | Then the last command should have thrown an exception of type "NodeAggregateIsDescendant" + + Scenario: Try to move a node to one of its grandchildren + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "sir-david-nodenborough" | + | newParentNodeAggregateId | "nodimus-mediocre" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeAggregateIsDescendant" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature index 4595c23e481..d3ffa539ad9 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature @@ -2128,220 +2128,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - # Legacy test cases - - Scenario: Move a complete node aggregate to a new parent after another of its new siblings - which does not exist in all variants - Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "younger-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "spec"} | - | nodeVariantSelectionStrategy | "allSpecializations" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "general"} | - | newParentNodeAggregateId | "sir-david-nodenborough" | - | newPrecedingSiblingNodeAggregateId | "elder-mc-nodeface" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have no succeeding siblings - - Scenario: Move a complete node aggregate to a new parent after the last of its new siblings - with a predecessor which does not exist in all variants - Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "younger-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "spec"} | - | nodeVariantSelectionStrategy | "allSpecializations" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "general"} | - | newParentNodeAggregateId | "sir-david-nodenborough" | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have no succeeding siblings - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have no succeeding siblings - - Scenario: Move a single node in a node aggregate to a new parent after the last of its new siblings - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | "sir-david-nodenborough" | - | newSucceedingSiblingNodeAggregateId | null | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} - And I expect this node to have no preceding siblings - And I expect this node to have no succeeding siblings - - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have no succeeding siblings - - Scenario: Move a node and its specializations in a node aggregate to a new parent after the last of its new siblings - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | "sir-david-nodenborough" | - | newSucceedingSiblingNodeAggregateId | null | - | relationDistributionStrategy | "gatherSpecializations" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} - And I expect this node to have no preceding siblings - And I expect this node to have no succeeding siblings - - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have no succeeding siblings - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have no succeeding siblings - - Scenario: Move a complete node aggregate to a new parent between siblings with different parents in other variants - Given the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "elder-mc-nodeface" | - | dimensionSpacePoint | {"example": "spec"} | - | newParentNodeAggregateId | "bustling-mc-nodeface" | - | newSucceedingSiblingNodeAggregateId | null | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "general"} | - | newParentNodeAggregateId | "sir-david-nodenborough" | - | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | - | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | - | relationDistributionStrategy | "gatherAll" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - - # An explicitly given parent node aggregate identifier should overrule given sibling identifiers - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - - Scenario: Move a complete node aggregate between siblings with different parents in other variants (without explicit new parent) - Given the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "elder-mc-nodeface" | - | dimensionSpacePoint | {"example": "spec"} | - | newParentNodeAggregateId | "bustling-mc-nodeface" | - | newSucceedingSiblingNodeAggregateId | null | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "general"} | - | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | - | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | - | relationDistributionStrategy | "gatherAll" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "bustling-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;bustling-mc-nodeface;{"example": "general"} - And I expect this node to have no preceding siblings - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | + # Other test cases Scenario: Move a node that has no name Given the command CreateNodeAggregateWithNode is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ImplicitNewParent_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ImplicitNewParent_Dimensions.feature deleted file mode 100644 index 1ea51857ef9..00000000000 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ImplicitNewParent_Dimensions.feature +++ /dev/null @@ -1,5 +0,0 @@ -@contentrepository @adapters=DoctrineDBAL -Feature: Move a node with content dimensions - - As a user of the CR, when I move a node to new siblings and they belong to another parent (completely or in variants), - I expect them to be ignored. diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ScatteredChildren.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ScatteredChildren.feature new file mode 100644 index 00000000000..e8c00aec5fe --- /dev/null +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ScatteredChildren.feature @@ -0,0 +1,271 @@ +@contentrepository @adapters=DoctrineDBAL +Feature: Move a node with content dimensions + + As a user of the CR, when I move a node to a new parent, where that parent or requested siblings have been scattered, + I expect scattered parents to be considered and scattered siblings to be ignored. + The other way around, if a scattered node is moved, the variants are to be gathered again as specified in the command. + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | example | general, source, peer, spec | spec->source->general, peer->general | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:Document': [] + """ + 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 graph projection is fully up to date + And I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | + | sir-david-nodenborough | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | parent-document | + | elder-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | elder-document | + | nody-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | document | + | younger-mc-nodeface | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | younger-document | + | sir-nodeward-nodington-iii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | esquire | + | nodimus-mediocre | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | esquire-child | + | elder-destinode | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | elder-target-document | + | bustling-destinode | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | bustling-target-document | + | younger-destinode | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | younger-target-document | + | elder-child-destinode | Neos.ContentRepository.Testing:Document | nodimus-mediocre | elder-child-target-document | + | younger-child-destinode | Neos.ContentRepository.Testing:Document | nodimus-mediocre | younger-child-target-document | + + Scenario: Scatter a node aggregate by moving a specialization variant to a different parent. Then move another node to the node's parent as a new sibling + Given the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "bustling-destinode" | + | dimensionSpacePoint | {"example": "spec"} | + | newParentNodeAggregateId | "nodimus-mediocre" | + | newSucceedingSiblingNodeAggregateId | "younger-child-destinode" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | newSucceedingSiblingNodeAggregateIde | "bustling-destinode" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-destinode;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;bustling-destinode;{"example": "general"} | + | cs-identifier;younger-destinode;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-destinode;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-destinode;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + + Scenario: Scatter a node aggregate by moving a specialization variant to a different parent. Then move another node to the scattered node as a new child + Given the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "bustling-destinode" | + | dimensionSpacePoint | {"example": "spec"} | + | newParentNodeAggregateId | "nodimus-mediocre" | + | newSucceedingSiblingNodeAggregateId | "younger-child-destinode" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "bustling-destinode" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/bustling-target-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;bustling-destinode;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/bustling-target-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;bustling-destinode;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + + Scenario: Scatter a node aggregate by moving a specialization variant to a different parent. Then move both variants to a new succeeding sibling only present as sibling in the general one + Given the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "bustling-destinode" | + | dimensionSpacePoint | {"example": "spec"} | + | newParentNodeAggregateId | "nodimus-mediocre" | + | newSucceedingSiblingNodeAggregateId | "younger-child-destinode" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "bustling-destinode" | + | dimensionSpacePoint | {"example": "source"} | + | newSucceedingSiblingNodeAggregateId | "younger-destinode" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-destinode;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-destinode;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/esquire-child/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} + And I expect this node to be a child of node cs-identifier;nodimus-mediocre;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-child-destinode;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-child-destinode;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/esquire-child/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} + And I expect this node to be a child of node cs-identifier;nodimus-mediocre;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have no succeeding siblings + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-destinode;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-destinode;{"example": "general"} | + + Scenario: Scatter a node aggregate by moving a specialization variant to a different parent. Then let a sibling variant follow suit and move the sibling before the node in both variants. + Given the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "bustling-destinode" | + | dimensionSpacePoint | {"example": "spec"} | + | newParentNodeAggregateId | "nodimus-mediocre" | + | newSucceedingSiblingNodeAggregateId | "younger-child-destinode" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + And the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "elder-destinode" | + | dimensionSpacePoint | {"example": "spec"} | + | newParentNodeAggregateId | "nodimus-mediocre" | + | newPrecedingSiblingNodeAggregateId | "bustling-destinode" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "elder-destinode" | + | dimensionSpacePoint | {"example": "source"} | + | newSucceedingSiblingNodeAggregateId | "bustling-destinode" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "elder-destinode" and node path "esquire/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;bustling-destinode;{"example": "general"} | + | cs-identifier;younger-destinode;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "elder-destinode" and node path "esquire/esquire-child/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} + And I expect this node to be a child of node cs-identifier;nodimus-mediocre;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-child-destinode;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;bustling-destinode;{"example": "general"} | + | cs-identifier;younger-child-destinode;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "elder-destinode" and node path "esquire/esquire-child/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} + And I expect this node to be a child of node cs-identifier;nodimus-mediocre;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-child-destinode;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-child-destinode;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "elder-destinode" and node path "esquire/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} + And I expect this node to have no preceding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;bustling-destinode;{"example": "general"} | + | cs-identifier;younger-destinode;{"example": "general"} | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature new file mode 100644 index 00000000000..2c6641c53b4 --- /dev/null +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature @@ -0,0 +1,2790 @@ +@contentrepository @adapters=DoctrineDBAL +Feature: Move a node aggregate into and out of a tagged parent + + As a user of the CR I want to move a node that + - is untagged + - tags itself (partially or completely) + - is tagged by one of its ancestors (partially or completely) + + to a new parent that + - is untagged (except when the source is untagged too, which is covered by the other move test cases) + - tags itself, same or differently than the source (partially or completely) + - is tagged by one of its ancestors, same or differently than the source (partially or completely) + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | example | general, source, peer, spec | spec->source->general, peer->general | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:Document': [] + """ + 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 graph projection is fully up to date + And I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | nodeName | + | sir-david-nodenborough | lady-eleonode-rootford | Neos.ContentRepository.Testing:Document | parent-document | + | nody-mc-nodeface | sir-david-nodenborough | Neos.ContentRepository.Testing:Document | document | + | nodimus-mediocre | nody-mc-nodeface | Neos.ContentRepository.Testing:Document | child-document | + | sir-nodeward-nodington-iii | lady-eleonode-rootford | Neos.ContentRepository.Testing:Document | esquire | + | nodimus-prime | sir-nodeward-nodington-iii | Neos.ContentRepository.Testing:Document | esquire-child | + + # move untagged to self-tagging + + Scenario: Move an untagged node to a new parent that tags itself + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move an untagged node to a new parent that tags itself partially + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + # move untagged to tagged + + Scenario: Move an untagged node to a new parent that is tagged by its ancestors + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move an untagged node to a new parent that is partially tagged by its ancestors + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + # move tagging to untagged + + Scenario: Move a node that tags itself to a new, untagged parent + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself partially to a new, untagged parent + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + # move tagging to tagging + + Scenario: Move a node that tags itself to a new parent that tags the same + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself to a new parent that tags the same, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself partially to a new parent that tags the same + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself partially to a new parent that tags the same, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself to a new parent that tags differently + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1,tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1,tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself to a new parent that tags differently, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1,tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself partially to a new parent that tags differently + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1,tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself partially to a new parent that tags differently, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1,tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + # move tagging to tagged + + Scenario: Move a node that tags itself to a new parent that is tagged the same + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself to a new parent that is tagged the same, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself partially to a new parent that is tagged the same + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself partially to a new parent that is tagged the same, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself to a new parent that is tagged differently + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1,tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1,tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself to a new parent that is tagged differently, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1,tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself partially to a new parent that tags differently + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1,tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a node that tags itself partially to a new parent that is tagged differently, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1,tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + # move tagged to untagged + + Scenario: Move a tagged node to a new, untagged parent + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a partially tagged node to a new, untagged parent + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + # move tagged to tagging + + Scenario: Move a tagged node to a new parent that tags the same + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nodimus-prime" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime,{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a tagged node to a new parent that tags the same, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nodimus-prime" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "tag1" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a partially tagged node to a new parent that tags the same + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nodimus-prime" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a partially tagged node to a new parent that tags the same, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nodimus-prime" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a tagged node to a new parent that tags differently + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nodimus-prime" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a tagged node to a new parent that tags differently, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nodimus-prime" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-devid-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a partially tagged node to a new parent that tags differently + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nodimus-prime" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a partially tagged node to a new parent that tags differently, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "nodimus-prime" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + # move tagged to tagged + + Scenario: Move a tagged node to a new parent that is tagged the same + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a tagged node to a new parent that is tagged the same, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a partially tagged node to a new parent that is tagged the same + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a partially tagged node to a new parent that is tagged the same, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag1" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a tagged node to a new parent that is tagged differently + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a tagged node to a new parent that is tagged differently, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a partially tagged node to a new parent that is tagged differently + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + Scenario: Move a partially tagged node to a new parent that is tagged differently, partially + + Given the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag1" | + And the graph projection is fully up to date + + And the command TagSubtree is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | coveredDimensionSpacePoint | {"example": "spec"} | + | nodeVariantSelectionStrategy | "allSpecializations" | + | tag | "tag2" | + And the graph projection is fully up to date + + When the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "nodimus-prime" | + | relationDistributionStrategy | "gatherSpecializations" | + And the graph projection is fully up to date + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "tag2" + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" + + And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} + And I expect this node to tag with "" + And I expect this node to be tagged with "" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature new file mode 100644 index 00000000000..c513bd266d4 --- /dev/null +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature @@ -0,0 +1,6 @@ +@contentrepository @adapters=DoctrineDBAL +Feature: Additional constraint checks after move node capabilities are introduced + + Scenario: Scatter a named node aggregate among different parents, then try to create a new node with the same name under one of the new parents + + Scenario: Scatter a node aggregate among different parents, then try to create a new child node of a type prohibited by one of the grandparents diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregateWithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/07-MoveNodeAggregateWithoutDimensions.feature similarity index 100% rename from Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregateWithoutDimensions.feature rename to Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/07-MoveNodeAggregateWithoutDimensions.feature diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregateConsideringDisableStateWithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregateConsideringDisableStateWithoutDimensions.feature deleted file mode 100644 index e8f664d3ec1..00000000000 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/MoveNodeAggregateConsideringDisableStateWithoutDimensions.feature +++ /dev/null @@ -1,343 +0,0 @@ -@contentrepository @adapters=DoctrineDBAL -Feature: Move a node aggregate considering disable state but without content dimensions - - As a user of the CR I want to move a node that - - is disabled by one of its ancestors - - disables itself - - disables any of its descendants - - is enabled - - to a new parent that - - is enabled - - disables itself - - is disabled by one of its ancestors - - These are the test cases without content dimensions being involved - - Background: - Given using no content dimensions - And using the following node types: - """yaml - 'Neos.ContentRepository.Testing:Document': [] - """ - 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 graph projection is fully up to date - And I am in the active content stream of workspace "live" - And the command CreateRootNodeAggregateWithNode is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-eleonode-rootford" | - | nodeTypeName | "Neos.ContentRepository:Root" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {} | - | coveredDimensionSpacePoints | [{}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "document" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nody-mc-nodeface" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {} | - | coveredDimensionSpacePoints | [{}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeName | "child-document" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-nodeward-nodington-iii" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {} | - | coveredDimensionSpacePoints | [{}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "esquire" | - | nodeAggregateClassification | "regular" | - And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nodimus-prime" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {} | - | coveredDimensionSpacePoints | [{}] | - | parentNodeAggregateId | "sir-nodeward-nodington-iii" | - | nodeName | "esquire-child" | - | nodeAggregateClassification | "regular" | - And the graph projection is fully up to date - - Scenario: Move a node disabled by one of its ancestors to a new parent that is enabled - Given the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {} | - | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - - # node aggregate occupation and coverage is not relevant without dimensions and thus not tested - - And I am in the active content stream of workspace "live" and dimension space point {} - And VisibilityConstraints are set to "frontend" - And I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/child-document" to lead to node cs-identifier;nody-mc-nodeface;{} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{} - - Scenario: Move a node disabled by itself to a new parent that is enabled - Given the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nody-mc-nodeface" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {} | - | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} - And VisibilityConstraints are set to "frontend" - - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/child-document" to lead to no node - - Scenario: Move a node that disables one of its descendants to a new parent that is enabled - Given the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "sir-david-nodenborough" | - | dimensionSpacePoint | {} | - | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} - And VisibilityConstraints are set to "frontend" - - Then I expect node aggregate identifier "sir-david-nodenborough" and node path "esquire/document" to lead to no node - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document/child-document" to lead to no node - - Scenario: Move a node that is disabled by one of its ancestors to a new parent that disables itself - Given the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - And the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-nodeward-nodington-iii" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {} | - | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} - And VisibilityConstraints are set to "frontend" - - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/child-document" to lead to no node - - Scenario: Move a node that is disabled by itself to a new parent that disables itself - Given the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - And the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-nodeward-nodington-iii" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "sir-david-nodenborough" | - | dimensionSpacePoint | {} | - | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} - And VisibilityConstraints are set to "frontend" - - Then I expect node aggregate identifier "sir-david-nodenborough" and node path "esquire/document" to lead to no node - - Scenario: Move a node that is enabled to a new parent that disables itself - Given the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-nodeward-nodington-iii" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "sir-david-nodenborough" | - | dimensionSpacePoint | {} | - | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} - And VisibilityConstraints are set to "frontend" - - Then I expect node aggregate identifier "sir-david-nodenborough" and node path "esquire/document" to lead to no node - - Scenario: Move a node that disables any of its descendants to a new parent that disables itself - Given the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - And the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-nodeward-nodington-iii" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "sir-david-nodenborough" | - | dimensionSpacePoint | {} | - | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} - And VisibilityConstraints are set to "frontend" - - Then I expect node aggregate identifier "sir-david-nodenborough" and node path "esquire/document" to lead to no node - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document/child-document" to lead to no node - - Scenario: Move a node that is disabled by one of its ancestors to a new parent that is disabled by one of its ancestors - Given the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - And the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-nodeward-nodington-iii" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {} | - | newParentNodeAggregateId | "nodimus-prime" | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} - And VisibilityConstraints are set to "frontend" - - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/child-document" to lead to no node - - Scenario: Move a node that is disabled by itself to a new parent that is disabled by one of its ancestors - Given the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - And the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-nodeward-nodington-iii" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "sir-david-nodenborough" | - | dimensionSpacePoint | {} | - | newParentNodeAggregateId | "nodimus-prime" | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} - And VisibilityConstraints are set to "frontend" - - Then I expect node aggregate identifier "sir-david-nodenborough" and node path "esquire/esquire-child/document" to lead to no node - - Scenario: Move a node that disables any of its descendants to a new parent that is disabled by one of its ancestors - Given the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - And the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-nodeward-nodington-iii" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "sir-david-nodenborough" | - | dimensionSpacePoint | {} | - | newParentNodeAggregateId | "nodimus-prime" | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} - And VisibilityConstraints are set to "frontend" - - Then I expect node aggregate identifier "sir-david-nodenborough" and node path "esquire/esquire-child/document" to lead to no node - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document/child-document" to lead to no node - - Scenario: Move a node that is enabled to a new parent that is disabled by one of its ancestors - Given the event SubtreeWasTagged was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-nodeward-nodington-iii" | - | affectedDimensionSpacePoints | [{}] | - | tag | "disabled" | - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "sir-david-nodenborough" | - | dimensionSpacePoint | {} | - | newParentNodeAggregateId | "nodimus-prime" | - | newSucceedingSiblingNodeAggregateId | null | - And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} - And VisibilityConstraints are set to "frontend" - - Then I expect node aggregate identifier "sir-david-nodenborough" and node path "esquire/esquire-child/document" to lead to no node diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php index 8317643ab22..d53233c328a 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php @@ -33,8 +33,8 @@ final readonly class NodeTags implements \IteratorAggregate, \Countable, \JsonSerializable { private function __construct( - private SubtreeTags $tags, - private SubtreeTags $inheritedTags, + public SubtreeTags $tags, + public SubtreeTags $inheritedTags, ) { } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index 0be5327d92b..85cea48b444 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -650,6 +650,28 @@ public function iExpectThisNodeToHaveTheFollowingSucceedingSiblings(TableNode $e }); } + /** + * @Then /^I expect this node to tag with "(.*)"$/ + * @param string $tagList the comma-separated list of tag names + */ + public function iExpectThisNodeToTagWith(string $tagList): void + { + $this->assertOnCurrentNode(function (Node $currentNode) use ($tagList) { + $currentNode->tags->tags->toStringArray() === explode(',', $tagList); + }); + } + + /** + * @Then /^I expect this node to be tagged with "(.*)"$/ + * @param string $tagList the comma-separated list of tag names + */ + public function iExpectThisNodeToBeTaggedWith(string $tagList): void + { + $this->assertOnCurrentNode(function (Node $currentNode) use ($tagList) { + $currentNode->tags->inheritedTags->toStringArray() === explode(',', $tagList); + }); + } + /** * @Then /^I expect this node to have no succeeding siblings$/ */ From 658044528470917ba29106f5f49a2ce6325ffccb Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 13 Apr 2024 23:35:20 +0200 Subject: [PATCH 022/214] Add creation constraint checks after node move --- .../06-AdditionalConstraintChecks.feature | 75 ++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature index c513bd266d4..a3ecd838bde 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature @@ -1,6 +1,79 @@ @contentrepository @adapters=DoctrineDBAL Feature: Additional constraint checks after move node capabilities are introduced + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | example | general, source, peer, spec | spec->source->general, peer->general | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:Document': [] + """ + 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 graph projection is fully up to date + And I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | + | sir-david-nodenborough | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | parent-document | + | lady-abigail-nodenborough | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | other-parent-document | + | sir-nodeward-nodington-iii | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | esquire | + | general-nodesworth | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | general-document | + | bustling-mc-nodeface | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | document | + Scenario: Scatter a named node aggregate among different parents, then try to create a new node with the same name under one of the new parents + Given the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "bustling-mc-nodeface" | + | dimensionSpacePoint | {"example": "spec"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + And the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "bustling-mc-nodeface" | + | dimensionSpacePoint | {"example": "peer"} | + | newParentNodeAggregateId | "lady-abigail-nodenborough" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + And the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "bustling-mc-nodeface" | + | dimensionSpacePoint | {"example": "general"} | + | newParentNodeAggregateId | "general-nodesworth" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | nodeTypeName | "Neos.ContentRepository.Testing:Document" | + | parentNodeAggregateId | "sir-nodeward-nodington-iii" | + | nodeName | "document" | + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + When the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | nodeTypeName | "Neos.ContentRepository.Testing:Document" | + | parentNodeAggregateId | "lady-abigail-nodenborough" | + | nodeName | "document" | + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + When the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | nodeTypeName | "Neos.ContentRepository.Testing:Document" | + | parentNodeAggregateId | "general-nodesworth" | + | nodeName | "document" | + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + - Scenario: Scatter a node aggregate among different parents, then try to create a new child node of a type prohibited by one of the grandparents From bddda1cf65c67198b7afac5151df90eb3c96e6fd Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sun, 14 Apr 2024 10:03:07 +0200 Subject: [PATCH 023/214] Enforce additional sibling constraint checks on MoveNode --- .../01-MoveNodes_ConstraintChecks.feature | 22 +++--- .../Feature/Common/ConstraintChecks.php | 67 +++++++++++++++++++ .../Classes/Feature/NodeMove/NodeMove.php | 66 ++++++++++++++++-- .../Classes/Projection/ContentGraph/Nodes.php | 11 +++ 4 files changed, 151 insertions(+), 15 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature index 34dabe37aa4..47ae7031caf 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature @@ -9,8 +9,8 @@ Feature: Move node to a new parent / within the current parent before a sibling Background: Given using the following content dimensions: - | Identifier | Values | Generalizations | - | example | general, source, spec | spec->source->general | + | Identifier | Values | Generalizations | + | example | general, source, spec, peer | spec->source->general, peer->general | And using the following node types: """yaml 'Neos.ContentRepository.Testing:Document': [] @@ -46,10 +46,12 @@ Feature: Move node to a new parent / within the current parent before a sibling And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | tetheredDescendantNodeAggregateIds | | sir-david-nodenborough | {"example": "source"} | Neos.ContentRepository.Testing:DocumentWithTetheredChildNode | lady-eleonode-rootford | document | {"tethered": "nodewyn-tetherton"} | - | nodimus-mediocre | {"example": "source"} | Neos.ContentRepository.Testing:Document | nodewyn-tetherton | grandchild-document | {} | | sir-nodeward-nodington-iii | {"example": "source"} | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | esquire | {} | | anthony-destinode | {"example": "spec"} | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | target-document | {} | | lady-abigail-nodenborough | {"example": "spec"} | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | child-target-document | {} | + | nodimus-prime | {"example": "source"} | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | child-document | {} | + | nodimus-mediocre | {"example": "source"} | Neos.ContentRepository.Testing:Document | nodimus-prime | grandchild-document | {} | + | general-nodesworth | {"example": "general"} | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | general-document | {} | Scenario: Try to move a node in a non-existing workspace: When the command MoveNodeAggregate is executed with payload and exceptions are caught: @@ -189,8 +191,8 @@ Feature: Move node to a new parent / within the current parent before a sibling Scenario: Using the gatherAll strategy, try to move a node to a parent that already has a child node of the same name in a generalization Given the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | - | rival-destinode | {"example": "general"} | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | target-document | - | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | target-document | + | rival-destinode | {"example": "general"} | Neos.ContentRepository.Testing:Document | general-nodesworth | target-document | + | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Document | nodimus-prime | target-document | # Remove the node with the conflicting name in all variants except the generalization And the command RemoveNodeAggregate is executed with payload: | Key | Value | @@ -209,7 +211,7 @@ Feature: Move node to a new parent / within the current parent before a sibling | Key | Value | | dimensionSpacePoint | {"example": "source"} | | nodeAggregateId | "nody-mc-nodeface" | - | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | newParentNodeAggregateId | "general-nodesworth" | | relationDistributionStrategy | "gatherAll" | Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" @@ -265,12 +267,12 @@ Feature: Move node to a new parent / within the current parent before a sibling Scenario: Try to move existing node after a node which is not a child of the new parent When the command MoveNodeAggregate is executed with payload and exceptions are caught: | Key | Value | - | dimensionSpacePoint | {"example": "source"} | + | dimensionSpacePoint | {"example": "spec"} | | nodeAggregateId | "sir-david-nodenborough" | | newParentNodeAggregateId | "anthony-destinode" | | newPrecedingSiblingNodeAggregateId | "sir-nodeward-nodington-iii" | | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeAggregateIsNoSibling" + Then the last command should have thrown an exception of type "NodeAggregateIsNoChild" Scenario: Try to move existing node to a non-existing succeeding sibling When the command MoveNodeAggregate is executed with payload and exceptions are caught: @@ -293,12 +295,12 @@ Feature: Move node to a new parent / within the current parent before a sibling Scenario: Try to move existing node before a node which is not a child of the new parent When the command MoveNodeAggregate is executed with payload and exceptions are caught: | Key | Value | - | dimensionSpacePoint | {"example": "source"} | + | dimensionSpacePoint | {"example": "spec"} | | nodeAggregateId | "sir-david-nodenborough" | | newParentNodeAggregateId | "anthony-destinode" | | newSucceedingSiblingNodeAggregateId | "sir-nodeward-nodington-iii" | | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "NodeAggregateIsNoSibling" + Then the last command should have thrown an exception of type "NodeAggregateIsNoChild" Scenario: Try to move a node to one of its children When the command MoveNodeAggregate is executed with payload and exceptions are caught: diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index b4e665910d4..f8bf6372262 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -27,7 +27,11 @@ use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindPrecedingSiblingNodesFilter; +use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; +use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamIsClosed; use Neos\ContentRepository\Core\SharedModel\Exception\DimensionSpacePointIsNotYetOccupied; @@ -36,6 +40,8 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateDoesCurrentlyNotCoverDimensionSpacePointSet; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateIsDescendant; +use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateIsNoChild; +use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateIsNoSibling; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateIsRoot; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateIsTethered; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; @@ -557,6 +563,67 @@ protected function requireNodeAggregateToNotBeDescendant( } } + /** + * @throws NodeAggregateIsNoSibling + */ + protected function requireNodeAggregateToBeSibling( + ContentStreamId $contentStreamId, + NodeAggregateId $referenceNodeAggregateId, + NodeAggregateId $siblingNodeAggregateId, + DimensionSpacePoint $dimensionSpacePoint, + ContentRepository $contentRepository, + ): void { + $succeedingSiblings = $contentRepository->getContentGraph()->getSubgraph( + $contentStreamId, + $dimensionSpacePoint, + VisibilityConstraints::withoutRestrictions() + )->findSucceedingSiblingNodes($referenceNodeAggregateId, FindSucceedingSiblingNodesFilter::create()); + if ($succeedingSiblings->getIds()->contain($siblingNodeAggregateId)) { + return; + } + + $precedingSiblings = $contentRepository->getContentGraph()->getSubgraph( + $contentStreamId, + $dimensionSpacePoint, + VisibilityConstraints::withoutRestrictions() + )->findPrecedingSiblingNodes($referenceNodeAggregateId, FindPrecedingSiblingNodesFilter::create()); + if ($precedingSiblings->getIds()->contain($siblingNodeAggregateId)) { + return; + } + + throw NodeAggregateIsNoSibling::butWasExpectedToBeInDimensionSpacePoint( + $siblingNodeAggregateId, + $referenceNodeAggregateId, + $dimensionSpacePoint + ); + } + + /** + * @throws NodeAggregateIsNoChild + */ + protected function requireNodeAggregateToBeChild( + ContentStreamId $contentStreamId, + NodeAggregateId $childNodeAggregateId, + NodeAggregateId $parentNodeAggregateId, + DimensionSpacePoint $dimensionSpacePoint, + ContentRepository $contentRepository, + ): void { + $childNodes = $contentRepository->getContentGraph()->getSubgraph( + $contentStreamId, + $dimensionSpacePoint, + VisibilityConstraints::withoutRestrictions() + )->findChildNodes($parentNodeAggregateId, FindChildNodesFilter::create()); + if ($childNodes->getIds()->contain($childNodeAggregateId)) { + return; + } + + throw NodeAggregateIsNoChild::butWasExpectedToBeInDimensionSpacePoint( + $childNodeAggregateId, + $parentNodeAggregateId, + $dimensionSpacePoint + ); + } + /** * @throws NodeNameIsAlreadyOccupied */ diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index 9290dfdb872..21f63678cc8 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace; +use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; @@ -43,6 +44,8 @@ use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateCurrentlyDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateIsDescendant; +use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateIsNoChild; +use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateIsNoSibling; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -62,13 +65,30 @@ abstract protected function requireProjectedNodeAggregate( ContentRepository $contentRepository ): NodeAggregate; + abstract protected function requireNodeAggregateToBeSibling( + ContentStreamId $contentStreamId, + NodeAggregateId $referenceNodeAggregateId, + NodeAggregateId $siblingNodeAggregateId, + DimensionSpacePoint $dimensionSpacePoint, + ContentRepository $contentRepository, + ): void; + + abstract protected function requireNodeAggregateToBeChild( + ContentStreamId $contentStreamId, + NodeAggregateId $childNodeAggregateId, + NodeAggregateId $parentNodAggregateId, + DimensionSpacePoint $dimensionSpacePoint, + ContentRepository $contentRepository, + ): void; + /** - * @return EventsToPublish * @throws ContentStreamDoesNotExistYet * @throws NodeAggregatesTypeIsAmbiguous * @throws NodeAggregateCurrentlyDoesNotExist * @throws DimensionSpacePointNotFound * @throws NodeAggregateIsDescendant + * @throws NodeAggregateIsNoSibling + * @throws NodeAggregateIsNoChild */ private function handleMoveNodeAggregate( MoveNodeAggregate $command, @@ -101,17 +121,19 @@ private function handleMoveNodeAggregate( $contentRepository ); - $this->requireNodeNameToBeUncovered( + $newParentNodeAggregate = $this->requireProjectedNodeAggregate( $contentStreamId, - $nodeAggregate->nodeName, $command->newParentNodeAggregateId, - $affectedDimensionSpacePoints, $contentRepository ); - $newParentNodeAggregate = $this->requireProjectedNodeAggregate( + $this->requireNodeNameToBeUncovered( $contentStreamId, + $nodeAggregate->nodeName, $command->newParentNodeAggregateId, + // We need to check all covered DSPs of the parent node aggregate to prevent siblings + // with different node aggregate IDs but the same name + $newParentNodeAggregate->coveredDimensionSpacePoints, $contentRepository ); @@ -134,6 +156,23 @@ private function handleMoveNodeAggregate( $command->newPrecedingSiblingNodeAggregateId, $contentRepository ); + if ($command->newParentNodeAggregateId) { + $this->requireNodeAggregateToBeChild( + $contentStreamId, + $command->newPrecedingSiblingNodeAggregateId, + $command->newParentNodeAggregateId, + $command->dimensionSpacePoint, + $contentRepository + ); + } else { + $this->requireNodeAggregateToBeSibling( + $contentStreamId, + $command->nodeAggregateId, + $command->newPrecedingSiblingNodeAggregateId, + $command->dimensionSpacePoint, + $contentRepository + ); + } } if ($command->newSucceedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( @@ -141,6 +180,23 @@ private function handleMoveNodeAggregate( $command->newSucceedingSiblingNodeAggregateId, $contentRepository ); + if ($command->newParentNodeAggregateId) { + $this->requireNodeAggregateToBeChild( + $contentStreamId, + $command->newSucceedingSiblingNodeAggregateId, + $command->newParentNodeAggregateId, + $command->dimensionSpacePoint, + $contentRepository + ); + } else { + $this->requireNodeAggregateToBeSibling( + $contentStreamId, + $command->nodeAggregateId, + $command->newSucceedingSiblingNodeAggregateId, + $command->dimensionSpacePoint, + $contentRepository + ); + } } /** @var OriginNodeMoveMapping[] $originNodeMoveMappings */ diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php index 1ea87139400..461ab934305 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php @@ -14,6 +14,9 @@ namespace Neos\ContentRepository\Core\Projection\ContentGraph; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; + /** * An immutable, type-safe collection of Node objects * @@ -194,4 +197,12 @@ public function nextAll(Node $referenceNode): self return new self(array_slice($this->nodes, $referenceNodeIndex + 1)); } + + public function getIds(): NodeAggregateIds + { + return NodeAggregateIds::create(...array_map( + fn (Node $node): NodeAggregateId => $node->nodeAggregateId, + $this->nodes + )); + } } From e82d47f667c06791beebcaa187180fe95acb1f71 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sun, 14 Apr 2024 13:24:27 +0200 Subject: [PATCH 024/214] WIP: Refactor NodeAggregateWasMoved to use InterdimensionalSiblings --- .../Domain/Projection/Feature/NodeMove.php | 165 ++++----- .../NodeMove/Dto/CoverageNodeMoveMapping.php | 75 ---- .../NodeMove/Dto/CoverageNodeMoveMappings.php | 88 ----- .../NodeMove/Dto/OriginNodeMoveMapping.php | 46 --- .../NodeMove/Dto/OriginNodeMoveMappings.php | 89 ----- .../Dto/ParentNodeMoveDestination.php | 87 ----- .../SucceedingSiblingNodeMoveDestination.php | 99 ----- .../NodeMove/Event/NodeAggregateWasMoved.php | 100 ++--- .../Classes/Feature/NodeMove/NodeMove.php | 341 +++++------------- .../Exception/NodeAggregateIsNoChild.php | 41 +++ .../Exception/NodeAggregateIsNoSibling.php | 38 ++ .../Classes/NodeDataToEventsProcessor.php | 28 +- .../Adjustment/TetheredNodeAdjustments.php | 45 +-- .../CatchUpHook/RouterCacheHook.php | 26 +- .../Projection/DocumentUriPathProjection.php | 54 +-- .../ChangeProjection.php | 29 +- 16 files changed, 374 insertions(+), 977 deletions(-) delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/CoverageNodeMoveMapping.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/CoverageNodeMoveMappings.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/OriginNodeMoveMapping.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/OriginNodeMoveMappings.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/ParentNodeMoveDestination.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/SucceedingSiblingNodeMoveDestination.php create mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Exception/NodeAggregateIsNoChild.php create mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Exception/NodeAggregateIsNoSibling.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php index f779ad9ff64..3cf3a3e1387 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php @@ -6,17 +6,14 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; -use Doctrine\DBAL\Driver\Exception; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\EventCouldNotBeAppliedToContentGraph; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; +use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSibling; use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMapping; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\ParentNodeMoveDestination; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\SucceedingSiblingNodeMoveDestination; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -39,44 +36,37 @@ abstract protected function getTableNamePrefix(): string; private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event): void { $this->transactional(function () use ($event) { - foreach ($event->nodeMoveMappings as $moveNodeMapping) { - // for each materialized node in the DB which we want to adjust, we have one MoveNodeMapping. - $nodeToBeMoved = $this->getProjectionContentGraph()->findNodeByIds( + foreach ($event->succeedingSiblingsForCoverage as $succeedingSiblingForCoverage) { + $nodeToBeMoved = $this->getProjectionContentGraph()->findNodeInAggregate( $event->contentStreamId, $event->nodeAggregateId, - $moveNodeMapping->movedNodeOrigin + $succeedingSiblingForCoverage->dimensionSpacePoint ); if (is_null($nodeToBeMoved)) { throw EventCouldNotBeAppliedToContentGraph::becauseTheSourceNodeIsMissing(get_class($event)); } - foreach ($moveNodeMapping->newLocations as $newLocation) { - assert($newLocation instanceof CoverageNodeMoveMapping); - - $affectedDimensionSpacePoints = new DimensionSpacePointSet([ - $newLocation->coveredDimensionSpacePoint - ]); - - // do the move (depending on how the move target is specified) - $newParentNodeAggregateId = match ($newLocation->destination::class) { - SucceedingSiblingNodeMoveDestination::class => $this->moveNodeBeforeSucceedingSibling( - $event->contentStreamId, - $nodeToBeMoved, - $newLocation->coveredDimensionSpacePoint, - $newLocation->destination - ), - ParentNodeMoveDestination::class => $newLocation->destination->nodeAggregateId, - }; - if ($newLocation->destination instanceof ParentNodeMoveDestination) { - $this->moveNodeIntoParent( - $event->contentStreamId, - $nodeToBeMoved, - $newLocation->coveredDimensionSpacePoint, - $newLocation->destination - ); - } - $this->moveSubtreeTags($event->contentStreamId, $event->nodeAggregateId, $newParentNodeAggregateId, $newLocation->coveredDimensionSpacePoint); + if ($event->newParentNodeAggregateId) { + $this->moveNodeIntoParent( + $event->contentStreamId, + $nodeToBeMoved, + $event->newParentNodeAggregateId, + $succeedingSiblingForCoverage + ); + $this->moveSubtreeTags( + $event->contentStreamId, + $event->nodeAggregateId, + $event->newParentNodeAggregateId, + $succeedingSiblingForCoverage->dimensionSpacePoint + ); + } else { + $this->moveNodeBeforeSucceedingSibling( + $event->contentStreamId, + $nodeToBeMoved, + $succeedingSiblingForCoverage, + ); + // subtree tags stay the same if the parent doesn't change } } }); @@ -84,90 +74,72 @@ private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event): void /** * This helper is responsible for moving a single incoming HierarchyRelation of $nodeToBeMoved - * to a new location. $coveredDimensionSpacePointWhereMoveShouldHappen specifies which incoming HierarchyRelation - * should be moved. + * to a new location without changing the parent. $succeedingSiblingForCoverage specifies + * which incoming HierarchyRelation should be moved and where exactly. * * The move target is given as $succeedingSiblingNodeMoveTarget. This also specifies the new parent node. * @throws \Exception - * @throws Exception - * @return NodeAggregateId the PARENT's NodeAggregateId */ private function moveNodeBeforeSucceedingSibling( ContentStreamId $contentStreamId, NodeRecord $nodeToBeMoved, - DimensionSpacePoint $coveredDimensionSpacePointWhereMoveShouldHappen, - SucceedingSiblingNodeMoveDestination $succeedingSiblingNodeMoveDestination, - ): NodeAggregateId { + InterdimensionalSibling $succeedingSiblingForCoverage, + ): void { $projectionContentGraph = $this->getProjectionContentGraph(); // find the single ingoing hierarchy relation which we want to move $ingoingHierarchyRelation = $this->findIngoingHierarchyRelationToBeMoved( $nodeToBeMoved, $contentStreamId, - $coveredDimensionSpacePointWhereMoveShouldHappen + $succeedingSiblingForCoverage->dimensionSpacePoint ); - // find the new succeeding sibling NodeRecord; and the new parent NodeRecord (which is the - // succeeding sibling's parent). We need these records because we'll use their RelationAnchorPoints - // later. - $newSucceedingSibling = $projectionContentGraph->findNodeByIds( - $contentStreamId, - $succeedingSiblingNodeMoveDestination->nodeAggregateId, - $succeedingSiblingNodeMoveDestination->originDimensionSpacePoint - ); - if ($newSucceedingSibling === null) { - throw EventCouldNotBeAppliedToContentGraph::becauseTheTargetSucceedingSiblingNodeIsMissing( - MoveNodeAggregate::class - ); - } - - $newParent = $projectionContentGraph->findNodeByIds( - $contentStreamId, - $succeedingSiblingNodeMoveDestination->parentNodeAggregateId, - $succeedingSiblingNodeMoveDestination->parentOriginDimensionSpacePoint, - ); - if ($newParent === null) { - // this should NEVER happen; because this would mean $newSucceedingSibling - // wouldn't have a parent => invariant violation. - throw EventCouldNotBeAppliedToContentGraph::becauseTheTargetSucceedingSiblingNodesParentIsMissing( - MoveNodeAggregate::class + $newSucceedingSibling = null; + if ($succeedingSiblingForCoverage->nodeAggregateId) { + // find the new succeeding sibling NodeRecord; We need this records because we'll use its RelationAnchorPoint later. + $newSucceedingSibling = $projectionContentGraph->findNodeInAggregate( + $contentStreamId, + $succeedingSiblingForCoverage->nodeAggregateId, + $succeedingSiblingForCoverage->dimensionSpacePoint ); + if ($newSucceedingSibling === null) { + throw EventCouldNotBeAppliedToContentGraph::becauseTheTargetSucceedingSiblingNodeIsMissing( + MoveNodeAggregate::class + ); + } } - // assign new position + // fetch... $newPosition = $this->getRelationPosition( - $newParent->relationAnchorPoint, + $ingoingHierarchyRelation->parentNodeAnchor, null, - $newSucceedingSibling->relationAnchorPoint, + $newSucceedingSibling?->relationAnchorPoint, $contentStreamId, - $coveredDimensionSpacePointWhereMoveShouldHappen + $succeedingSiblingForCoverage->dimensionSpacePoint ); - // this is the actual move - $ingoingHierarchyRelation->assignNewParentNode( - $newParent->relationAnchorPoint, + // ...and assign the new position + $ingoingHierarchyRelation->assignNewPosition( $newPosition, $this->getDatabaseConnection(), $this->getTableNamePrefix() ); - - return $newParent->nodeAggregateId; } /** * This helper is responsible for moving a single incoming HierarchyRelation of $nodeToBeMoved - * to a new location. $coveredDimensionSpacePointWhereMoveShouldHappen specifies which incoming HierarchyRelation - * should be moved. + * to a new location including a change of parent. $succeedingSiblingForCoverage specifies + * which incoming HierarchyRelation should be moved and where exactly. * - * The move target is given as $parentNodeMoveTarget. We always move to the END of the children list of the - * given parent. + * The move target is given as $parentNodeAggregateId and $succeedingSiblingForCoverage. + * We always move to parent after the succeeding sibling if given (or to the end) * @throws DBALException */ private function moveNodeIntoParent( ContentStreamId $contentStreamId, NodeRecord $nodeToBeMoved, - DimensionSpacePoint $coveredDimensionSpacePointWhereMoveShouldHappen, - ParentNodeMoveDestination $parentNodeMoveDestination + NodeAggregateId $parentNodeAggregateId, + InterdimensionalSibling $succeedingSiblingForCoverage, ): void { $projectionContentGraph = $this->getProjectionContentGraph(); @@ -175,15 +147,14 @@ private function moveNodeIntoParent( $ingoingHierarchyRelation = $this->findIngoingHierarchyRelationToBeMoved( $nodeToBeMoved, $contentStreamId, - $coveredDimensionSpacePointWhereMoveShouldHappen + $succeedingSiblingForCoverage->dimensionSpacePoint ); - // find the new parent NodeRecord (specified by $parentNodeMoveTarget). - // We need this record because we'll use its RelationAnchorPoints later. - $newParent = $projectionContentGraph->findNodeByIds( + // find the new parent NodeRecord; We need this record because we'll use its RelationAnchorPoints later. + $newParent = $projectionContentGraph->findNodeInAggregate( $contentStreamId, - $parentNodeMoveDestination->nodeAggregateId, - $parentNodeMoveDestination->originDimensionSpacePoint + $parentNodeAggregateId, + $succeedingSiblingForCoverage->dimensionSpacePoint ); if ($newParent === null) { throw EventCouldNotBeAppliedToContentGraph::becauseTheTargetParentNodeIsMissing( @@ -191,14 +162,28 @@ private function moveNodeIntoParent( ); } + $newSucceedingSibling = null; + if ($succeedingSiblingForCoverage->nodeAggregateId) { + // find the new succeeding sibling NodeRecord; We need this record because we'll use its RelationAnchorPoint later. + $newSucceedingSibling = $projectionContentGraph->findNodeInAggregate( + $contentStreamId, + $succeedingSiblingForCoverage->nodeAggregateId, + $succeedingSiblingForCoverage->dimensionSpacePoint + ); + if ($newSucceedingSibling === null) { + throw EventCouldNotBeAppliedToContentGraph::becauseTheTargetSucceedingSiblingNodeIsMissing( + MoveNodeAggregate::class + ); + } + } + // assign new position $newPosition = $this->getRelationPosition( $newParent->relationAnchorPoint, null, - // move to end of children - null, + $newSucceedingSibling?->relationAnchorPoint, $contentStreamId, - $coveredDimensionSpacePointWhereMoveShouldHappen + $succeedingSiblingForCoverage->dimensionSpacePoint ); // this is the actual move diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/CoverageNodeMoveMapping.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/CoverageNodeMoveMapping.php deleted file mode 100644 index 47273e8118d..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/CoverageNodeMoveMapping.php +++ /dev/null @@ -1,75 +0,0 @@ - $array - */ - public static function fromArray(array $array): self - { - if (!empty($array['newSucceedingSibling'])) { - return new self( - DimensionSpacePoint::fromArray($array['coveredDimensionSpacePoint']), - SucceedingSiblingNodeMoveDestination::fromArray($array['newSucceedingSibling']), - ); - } elseif (!empty($array['newParent'])) { - return new self( - DimensionSpacePoint::fromArray($array['coveredDimensionSpacePoint']), - ParentNodeMoveDestination::fromArray($array['newParent']), - ); - } else { - throw new \RuntimeException('!!!'); - } - } - - /** - * @return array - */ - public function jsonSerialize(): array - { - if ($this->destination instanceof SucceedingSiblingNodeMoveDestination) { - return [ - 'coveredDimensionSpacePoint' => $this->coveredDimensionSpacePoint, - 'newSucceedingSibling' => $this->destination - ]; - } elseif ($this->destination instanceof ParentNodeMoveDestination) { - return [ - 'coveredDimensionSpacePoint' => $this->coveredDimensionSpacePoint, - 'newParent' => $this->destination - ]; - } else { - throw new \RuntimeException('!!!'); - } - } -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/CoverageNodeMoveMappings.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/CoverageNodeMoveMappings.php deleted file mode 100644 index 18b426add1b..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/CoverageNodeMoveMappings.php +++ /dev/null @@ -1,88 +0,0 @@ - - * @api DTO of {@see NodeAggregateWasMoved} event - */ -final class CoverageNodeMoveMappings implements \IteratorAggregate, \Countable, \JsonSerializable -{ - /** - * @var array - */ - private array $mappings; - - /** - * @param array $values - */ - private function __construct(array $values) - { - $this->mappings = $values; - } - - /** - * @param array|CoverageNodeMoveMapping> $mappings - */ - public static function fromArray(array $mappings): self - { - $processedMappings = []; - foreach ($mappings as $mapping) { - if (is_array($mapping)) { - $processedMappings[] = CoverageNodeMoveMapping::fromArray($mapping); - } elseif ($mapping instanceof CoverageNodeMoveMapping) { - $processedMappings[] = $mapping; - } else { - /** @var mixed $mapping */ - throw new \InvalidArgumentException( - sprintf( - 'Invalid NodeMoveMapping. Expected instance of %s, got: %s', - CoverageNodeMoveMapping::class, - is_object($mapping) ? get_class($mapping) : gettype($mapping) - ), - 1547811318 - ); - } - } - return new self($processedMappings); - } - - public static function create(CoverageNodeMoveMapping ...$coverageNodeMoveMappings): self - { - return new self(array_values($coverageNodeMoveMappings)); - } - - - /** - * @return \Traversable - */ - public function getIterator(): \Traversable - { - yield from $this->mappings; - } - - public function count(): int - { - return count($this->mappings); - } - - /** - * @return array - */ - public function jsonSerialize(): array - { - return $this->mappings; - } -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/OriginNodeMoveMapping.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/OriginNodeMoveMapping.php deleted file mode 100644 index 14bd2a5952b..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/OriginNodeMoveMapping.php +++ /dev/null @@ -1,46 +0,0 @@ - $array - */ - public static function fromArray(array $array): self - { - return new self( - OriginDimensionSpacePoint::fromArray($array['movedNodeOrigin']), - CoverageNodeMoveMappings::fromArray($array['newLocations']), - ); - } - - /** - * @return array - */ - public function jsonSerialize(): array - { - return [ - 'movedNodeOrigin' => $this->movedNodeOrigin, - 'newLocations' => $this->newLocations, - ]; - } -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/OriginNodeMoveMappings.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/OriginNodeMoveMappings.php deleted file mode 100644 index b7fa09da0eb..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/OriginNodeMoveMappings.php +++ /dev/null @@ -1,89 +0,0 @@ - - * @api DTO of {@see NodeAggregateWasMoved} event - */ -final class OriginNodeMoveMappings implements \IteratorAggregate, \Countable, \JsonSerializable -{ - /** - * @var array - */ - private array $mappings; - - /** - * @param array $values - */ - private function __construct(array $values) - { - $this->mappings = $values; - } - - /** - * @param array|OriginNodeMoveMapping> $mappings - */ - public static function fromArray(array $mappings): self - { - $processedMappings = []; - foreach ($mappings as $mapping) { - if (is_array($mapping)) { - $processedMappings[] = OriginNodeMoveMapping::fromArray($mapping); - } elseif ($mapping instanceof OriginNodeMoveMapping) { - $processedMappings[] = $mapping; - } else { - /** @var mixed $mapping */ - throw new \InvalidArgumentException( - sprintf( - 'Invalid NodeMoveMapping. Expected instance of %s, got: %s', - OriginNodeMoveMapping::class, - is_object($mapping) ? get_class($mapping) : gettype($mapping) - ), - 1547811318 - ); - } - } - return new self($processedMappings); - } - - public static function create(OriginNodeMoveMapping ...$originNodeMoveMappings): self - { - return new self(array_values($originNodeMoveMappings)); - } - - /** - * @return \Traversable - */ - public function getIterator(): \Traversable - { - yield from $this->mappings; - } - - public function count(): int - { - return count($this->mappings); - } - - /** - * @return array - */ - public function jsonSerialize(): array - { - return $this->mappings; - } -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/ParentNodeMoveDestination.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/ParentNodeMoveDestination.php deleted file mode 100644 index bfcbf872d10..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/ParentNodeMoveDestination.php +++ /dev/null @@ -1,87 +0,0 @@ - `ParentNodeMoveDestination(node1)` - * - * - you want to move the node INTO another node. - * ``` - * - node1 - * - node2 <-- source: we want to move this node - * - node3 - * - <-- destination - * ``` - * => `ParentNodeMoveDestination(node3)` - * - * For all other cases, use {@see SucceedingSiblingNodeMoveDestination}. - * - * @api DTO of {@see NodeAggregateWasMoved} event - */ -final readonly class ParentNodeMoveDestination implements \JsonSerializable -{ - private function __construct( - public NodeAggregateId $nodeAggregateId, - public OriginDimensionSpacePoint $originDimensionSpacePoint - ) { - } - - public static function create( - NodeAggregateId $nodeAggregateId, - OriginDimensionSpacePoint $originDimensionSpacePoint - ): self { - return new self($nodeAggregateId, $originDimensionSpacePoint); - } - - /** - * @param array $array - */ - public static function fromArray(array $array): self - { - return new self( - NodeAggregateId::fromString($array['nodeAggregateId']), - OriginDimensionSpacePoint::fromArray($array['originDimensionSpacePoint']) - ); - } - - /** - * @return array - */ - public function jsonSerialize(): array - { - return [ - 'nodeAggregateId' => $this->nodeAggregateId, - 'originDimensionSpacePoint' => $this->originDimensionSpacePoint, - ]; - } - - public function toJson(): string - { - return json_encode($this, JSON_THROW_ON_ERROR); - } -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/SucceedingSiblingNodeMoveDestination.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/SucceedingSiblingNodeMoveDestination.php deleted file mode 100644 index 182f1e80de2..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Dto/SucceedingSiblingNodeMoveDestination.php +++ /dev/null @@ -1,99 +0,0 @@ - `SucceedingSiblingNodeMoveDestination(node1)` - * - * - move nodes to a different parent, and before a specific sibling. - * ``` - * - node1 - * - <-- destination - * - node2 - * - node3 <-- source: we want to move this node - * ``` - * => `SucceedingSiblingNodeMoveDestination(node2)` - * - * @api DTO of {@see NodeAggregateWasMoved} event - */ -final readonly class SucceedingSiblingNodeMoveDestination implements \JsonSerializable -{ - private function __construct( - public NodeAggregateId $nodeAggregateId, - public OriginDimensionSpacePoint $originDimensionSpacePoint, - public NodeAggregateId $parentNodeAggregateId, - public OriginDimensionSpacePoint $parentOriginDimensionSpacePoint - ) { - } - - public static function create( - NodeAggregateId $nodeAggregateId, - OriginDimensionSpacePoint $originDimensionSpacePoint, - NodeAggregateId $parentNodeAggregateId, - OriginDimensionSpacePoint $parentOriginDimensionSpacePoint - ): self { - return new self( - $nodeAggregateId, - $originDimensionSpacePoint, - $parentNodeAggregateId, - $parentOriginDimensionSpacePoint - ); - } - - /** - * @param array $array - */ - public static function fromArray(array $array): self - { - return new self( - NodeAggregateId::fromString($array['nodeAggregateId']), - OriginDimensionSpacePoint::fromArray($array['originDimensionSpacePoint']), - NodeAggregateId::fromString($array['parentNodeAggregateId']), - OriginDimensionSpacePoint::fromArray($array['parentOriginDimensionSpacePoint']) - ); - } - - /** - * @return array - */ - public function jsonSerialize(): array - { - return [ - 'nodeAggregateId' => $this->nodeAggregateId, - 'originDimensionSpacePoint' => $this->originDimensionSpacePoint, - 'parentNodeAggregateId' => $this->parentNodeAggregateId, - 'parentOriginDimensionSpacePoint' => $this->parentOriginDimensionSpacePoint, - ]; - } - - public function toJson(): string - { - return json_encode($this, JSON_THROW_ON_ERROR); - } -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Event/NodeAggregateWasMoved.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Event/NodeAggregateWasMoved.php index a25538eef87..6bf52b0cf76 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Event/NodeAggregateWasMoved.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/Event/NodeAggregateWasMoved.php @@ -4,17 +4,17 @@ namespace Neos\ContentRepository\Core\Feature\NodeMove\Event; +use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\Common\EmbedsContentStreamAndNodeAggregateId; +use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSibling; +use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSiblings; use Neos\ContentRepository\Core\Feature\Common\PublishableToOtherContentStreamsInterface; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\OriginNodeMoveMappings; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\ParentNodeMoveDestination; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\SucceedingSiblingNodeMoveDestination; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** - * A node aggregate was moved in a content stream as defined in the node move mappings. + * A node aggregate was moved in a content stream * * We always move a node aggregate identified by a NodeAggregateId (in a given ContentStreamId); or * parts of the NodeAggregate. @@ -22,49 +22,29 @@ * The inner structure of the event is rather complicated, that's why the following picture shows it: * * ``` - *┌───────────────────────────┐ - *│ NodeAggregateWasMoved │ - *│-> contains NodeAggregateId│ - *└─────────┬─────────────────┘ + *┌───────────────────────────────────┐ + *│ NodeAggregateWasMoved │ + *│-> contains NodeAggregateId │ + *│-> contains parent NodeAggregateId │ + *└─────────┬─────────────────────────┘ * │ - * │ ┌────────────────────────────────────────────────┐ - * │ │ OriginNodeMoveMapping │ - * │ *│ -> contains OriginDimensionSpacePoint │ - * └───▶ │ - * │ (1 per OriginDimensionSpacePoint to be moved) │ - * └──────┬─────────────────────────────────────────┘ - * │ - * │ ┌───────────────────────────────────┐ - * │ │ CoverageNodeMoveMapping │ ┌─────────────────────────────┐ - * │ *│ -> coveredDimensionSpacePoint │ │ NoveMoveDestination │ - * └───▶ │ │ │ - * │ ?newSucceedingSibling, ?newParent ├───▶ nodeAggregateId │ - * │ (exactly one must be set) │ │ originDimensionSpacePoint │ - * │ │ └─────────────────────────────┘ - * │ (1 per coveredDimensionSpacePoint │ - * │ to be moved - for each edge in │ - * └───────────────────────────────────┘ + * │ ┌────────────────────────────────────────────────────┐ + * │ │ InterdimensionalSibling │ + * │ │ -> contains DimensionSpacePoint │ + * │ *│ -> contains succeeding sibling NodeAggregateId │ + * └───▶ │ + * │ (1 per affected dimension space point) │ + * └────────────────────────────────────────────────────┘ * ``` * * - We move some parts of a single NodeAggregate (`NodeAggregateWasMoved`). - * - For each OriginDimensionSpacePoint (where a materialized Node exists which should be moved), - * an `OriginNodeMoveMapping` exists. - * - For each Node which we want to move, we need to specify where the *incoming edges* of the node - * should be (if they should be moved). For each of these, a `CoverageNodeMoveMapping` exists. - * - * ## Specifying the Target of a Move inside `CoverageNodeMoveMapping` - * - * For a given `DimensionSpacePoint`, we specify the target node of the move as follows: - * - * - for *succeeding siblings* (see {@see SucceedingSiblingNodeMoveDestination}), we specify the - * newSucceedingSibling ID and its OriginDimensionSpacePoint; and additionally its parent ID - * and OriginDimensionSpacePoint. The parent is strictly redundant, but can ease projection - * implementer's lives. - * - * HINT: The parent is ALWAYS specified, so it is also specified *if it did not change*. - * - * - If you want to move something at the END of a children list (or as the first child, when no child - * exists yet), you specify `newParent` via {@see ParentNodeMoveDestination}. + * - If given, a single parent NodeAggregateId is provided and to be used for all affected DimensionSpacePoints. + * Else, no new parent will be set for any on the variants. + * - For each affected DimensionSpacePoint, an optional succeeding sibling is provided. + * -- If a single node is to be moved to the end, the succeeding sibling NodeAggregateId is null + * -- If a single node is to be moved to the start, the previous first sibling is to be set as succeeding sibling + * -- If a single node is not to be moved at all, e.g. if no siblings can be determined, it is considered unaffected + * and it (its DSP respectively) is not part of the InterdimensionalSibling collection * * @api events are the persistence-API of the content repository */ @@ -76,7 +56,8 @@ public function __construct( public ContentStreamId $contentStreamId, public NodeAggregateId $nodeAggregateId, - public OriginNodeMoveMappings $nodeMoveMappings, + public ?NodeAggregateId $newParentNodeAggregateId, + public InterdimensionalSiblings $succeedingSiblingsForCoverage, ) { } @@ -95,16 +76,43 @@ public function createCopyForContentStream(ContentStreamId $targetContentStreamI return new self( $targetContentStreamId, $this->nodeAggregateId, - $this->nodeMoveMappings, + $this->newParentNodeAggregateId, + $this->succeedingSiblingsForCoverage, ); } public static function fromArray(array $values): self { + if (array_key_exists('nodeMoveMappings', $values)) { + $newParentNodeAggregateId = null; + $succeedingSiblings = []; + foreach ($values['nodeMoveMappings'] as $nodeMoveMapping) { + // we don't care about origins anymore + foreach ($nodeMoveMapping['newLocations'] as $newLocation) { + if (array_key_exists('newParent', $newLocation)) { + $newParentNodeAggregateId = NodeAggregateId::fromString($newLocation['newParent']); + } + $succeedingSiblings[] = new InterdimensionalSibling( + DimensionSpacePoint::fromArray($newLocation['coveredDimensionSpacePoint']), + ($newLocation['newSucceedingSibling'] ?? null) + ? NodeAggregateId::fromString($newLocation['newSucceedingSibling']) + : null + ); + } + } + $succeedingSiblingsForCoverage = new InterdimensionalSiblings(...$succeedingSiblings); + } else { + $newParentNodeAggregateId = $values['newParentNodeAggregateId'] === null + ? null + : NodeAggregateId::fromString($values['newParentNodeAggregateId']); + $succeedingSiblingsForCoverage = InterdimensionalSiblings::fromArray($values['succeedingSiblingsForCoverage']); + } + return new self( ContentStreamId::fromString($values['contentStreamId']), NodeAggregateId::fromString($values['nodeAggregateId']), - OriginNodeMoveMappings::fromArray($values['nodeMoveMappings']), + $newParentNodeAggregateId, + $succeedingSiblingsForCoverage, ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index 21f63678cc8..15e173d89c0 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -19,27 +19,19 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; -use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; +use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSibling; +use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSiblings; use Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMapping; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMappings; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\OriginNodeMoveMapping; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\OriginNodeMoveMappings; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\ParentNodeMoveDestination; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\RelationDistributionStrategy; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\SucceedingSiblingNodeMoveDestination; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindPrecedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Pagination\Pagination; -use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; -use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateCurrentlyDoesNotExist; @@ -199,29 +191,20 @@ private function handleMoveNodeAggregate( } } - /** @var OriginNodeMoveMapping[] $originNodeMoveMappings */ - $originNodeMoveMappings = []; - foreach ($nodeAggregate->occupiedDimensionSpacePoints as $movedNodeOrigin) { - $originNodeMoveMappings[] = new OriginNodeMoveMapping( - $movedNodeOrigin, - $this->resolveCoverageNodeMoveMappings( - $contentStreamId, - $nodeAggregate, - $command->newParentNodeAggregateId, - $command->newPrecedingSiblingNodeAggregateId, - $command->newSucceedingSiblingNodeAggregateId, - $movedNodeOrigin, - $affectedDimensionSpacePoints, - $contentRepository - ) - ); - } - $events = Events::with( new NodeAggregateWasMoved( $contentStreamId, $command->nodeAggregateId, - OriginNodeMoveMappings::create(...$originNodeMoveMappings) + $command->newParentNodeAggregateId, + $this->resolveInterdimensionalSiblingsForMove( + $contentStreamId, + $command->dimensionSpacePoint, + $affectedDimensionSpacePoints, + $command->newSucceedingSiblingNodeAggregateId, + $command->newPrecedingSiblingNodeAggregateId, + $command->newParentNodeAggregateId !== null, + $contentRepository + ) ) ); @@ -239,43 +222,6 @@ private function handleMoveNodeAggregate( ); } - /** - * Resolves the new parents on a per-dimension-space-point basis - * - * If no parent node aggregate is defined, it will be resolved from the already evaluated new succeeding siblings. - * - * @todo move to content graph for more efficient calculation, if possible - */ - private function resolveNewParentAssignments( - /** The content stream the move operation is performed in */ - ContentStreamId $contentStreamId, - /** The parent node aggregate's id*/ - NodeAggregateId $parentId, - DimensionSpace\DimensionSpacePoint $coveredDimensionSpacePoint, - ContentRepository $contentRepository - ): CoverageNodeMoveMapping { - $contentSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $coveredDimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() - ); - $parentNode = $contentSubgraph->findNodeById($parentId); - if ($parentNode === null) { - throw new \InvalidArgumentException( - 'Parent ' . $parentId->value . ' not found in subgraph ' . json_encode($contentSubgraph), - 1667596931 - ); - } - - return CoverageNodeMoveMapping::createForNewParent( - $coveredDimensionSpacePoint, - ParentNodeMoveDestination::create( - $parentId, - $parentNode->originDimensionSpacePoint - ) - ); - } - private function resolveAffectedDimensionSpacePointSet( NodeAggregate $nodeAggregate, Dto\RelationDistributionStrategy $relationDistributionStrategy, @@ -292,204 +238,111 @@ private function resolveAffectedDimensionSpacePointSet( }; } - private function findSibling( - ContentSubgraphInterface $contentSubgraph, - ?NodeAggregateId $parentId, - NodeAggregateId $siblingId - ): ?Node { - $siblingCandidate = $contentSubgraph->findNodeById($siblingId); - if ($parentId && $siblingCandidate) { - // If a parent node aggregate is explicitly given, all siblings must have this parent - $parent = $contentSubgraph->findParentNode($siblingId); - if (is_null($parent)) { - throw new \InvalidArgumentException( - 'Parent ' . $parentId->value . ' not found in subgraph ' . json_encode($contentSubgraph), - 1645366837 - ); - } - if ($parent->nodeAggregateId->equals($parentId)) { - return $siblingCandidate; - } - } else { - return $siblingCandidate; - } - - return null; - } - - private function resolveSucceedingSiblingFromOriginSiblings( - NodeAggregateId $nodeAggregateId, - ?NodeAggregateId $parentId, - ?NodeAggregateId $precedingSiblingId, - ?NodeAggregateId $succeedingSiblingId, - ContentSubgraphInterface $currentContentSubgraph, - ContentSubgraphInterface $originContentSubgraph - ): ?Node { - $succeedingSibling = null; - $precedingSiblingCandidates = iterator_to_array( - $precedingSiblingId - ? $originContentSubgraph->findPrecedingSiblingNodes($precedingSiblingId, FindPrecedingSiblingNodesFilter::create()) - : Nodes::createEmpty() - ); - $succeedingSiblingCandidates = iterator_to_array( - $succeedingSiblingId - ? $originContentSubgraph->findSucceedingSiblingNodes( - $succeedingSiblingId, - FindSucceedingSiblingNodesFilter::create() - ) - : Nodes::createEmpty() - ); - /* @var $precedingSiblingCandidates Node[] */ - /* @var $succeedingSiblingCandidates Node[] */ - $maximumIndex = max(count($succeedingSiblingCandidates), count($precedingSiblingCandidates)); - for ($i = 0; $i < $maximumIndex; $i++) { - // try successors of same distance first - if (isset($succeedingSiblingCandidates[$i])) { - if ($succeedingSiblingCandidates[$i]->nodeAggregateId->equals($nodeAggregateId)) { - \array_splice($succeedingSiblingCandidates, $i, 1); - } - $succeedingSibling = $this->findSibling( - $currentContentSubgraph, - $parentId, - $succeedingSiblingCandidates[$i]->nodeAggregateId - ); - if ($succeedingSibling) { - break; - } - } - if (isset($precedingSiblingCandidates[$i])) { - /** @var NodeAggregateId $precedingSiblingId can only be the case if not null */ - if ($precedingSiblingCandidates[$i]->nodeAggregateId->equals($nodeAggregateId)) { - \array_splice($precedingSiblingCandidates, $i, 1); - } - $precedingSibling = $this->findSibling( - $currentContentSubgraph, - $parentId, - $precedingSiblingCandidates[$i]->nodeAggregateId - ); - if ($precedingSibling) { - $alternateSucceedingSiblings = $currentContentSubgraph->findSucceedingSiblingNodes( - $precedingSiblingId, - FindSucceedingSiblingNodesFilter::create(pagination: Pagination::fromLimitAndOffset(1, 0)), - ); - if (count($alternateSucceedingSiblings) > 0) { - $succeedingSibling = $alternateSucceedingSiblings->first(); - break; - } - } - } - } - - return $succeedingSibling; - } - - private function resolveCoverageNodeMoveMappings( - /** The content stream the move operation is performed in */ + /** + * @param bool $completeSet Whether unresolvable siblings should be added as null or not at all + * True when a new parent is set, which will result of the node being added at the end + * False when no new parent is set, which will result in the node not being moved + */ + private function resolveInterdimensionalSiblingsForMove( ContentStreamId $contentStreamId, - /** The node aggregate to be moved */ - NodeAggregate $nodeAggregate, - /** The parent node aggregate id, has precedence over siblings when in doubt */ - ?NodeAggregateId $parentId, - /** The planned preceding sibling's node aggregate id */ - ?NodeAggregateId $precedingSiblingId, - /** The planned succeeding sibling's node aggregate id */ - ?NodeAggregateId $succeedingSiblingId, - /** A dimension space point occupied by the node aggregate to be moved */ - OriginDimensionSpacePoint $originDimensionSpacePoint, - /** The dimension space points affected by the move operation */ + DimensionSpacePoint $selectedDimensionSpacePoint, DimensionSpacePointSet $affectedDimensionSpacePoints, - ContentRepository $contentRepository - ): CoverageNodeMoveMappings { - /** @var CoverageNodeMoveMapping[] $coverageNodeMoveMappings */ - $coverageNodeMoveMappings = []; - - $visibilityConstraints = VisibilityConstraints::withoutRestrictions(); - $originContentSubgraph = $contentRepository->getContentGraph()->getSubgraph( + ?NodeAggregateId $succeedingSiblingId, + ?NodeAggregateId $precedingSiblingId, + bool $completeSet, + ContentRepository $contentRepository, + ): InterdimensionalSiblings { + $selectedSubgraph = $contentRepository->getContentGraph()->getSubgraph( $contentStreamId, - $originDimensionSpacePoint->toDimensionSpacePoint(), - $visibilityConstraints + $selectedDimensionSpacePoint, + VisibilityConstraints::withoutRestrictions() ); - foreach ( - $nodeAggregate->getCoverageByOccupant($originDimensionSpacePoint) - ->getIntersection($affectedDimensionSpacePoints) as $dimensionSpacePoint - ) { - $contentSubgraph = $contentRepository->getContentGraph()->getSubgraph( + $alternativeSucceedingSiblingIds = $succeedingSiblingId + ? $selectedSubgraph->findSucceedingSiblingNodes( + $succeedingSiblingId, + FindSucceedingSiblingNodesFilter::create() + )->getIds() + : null; + $alternativePrecedingSiblingIds = $precedingSiblingId + ? $selectedSubgraph->findPrecedingSiblingNodes( + $precedingSiblingId, + FindPrecedingSiblingNodesFilter::create() + )->getIds() + : null; + + $interdimensionalSiblings = []; + foreach ($affectedDimensionSpacePoints as $dimensionSpacePoint) { + $variantSubgraph = $contentRepository->getContentGraph()->getSubgraph( $contentStreamId, $dimensionSpacePoint, - $visibilityConstraints + VisibilityConstraints::withoutRestrictions() ); - - $succeedingSibling = $succeedingSiblingId - ? $this->findSibling($contentSubgraph, $parentId, $succeedingSiblingId) - : null; - if (!$succeedingSibling) { - $precedingSibling = $precedingSiblingId - ? $this->findSibling($contentSubgraph, $parentId, $precedingSiblingId) - : null; - if ($precedingSiblingId && $precedingSibling) { - $alternateSucceedingSiblings = $contentSubgraph->findSucceedingSiblingNodes( - $precedingSiblingId, - FindSucceedingSiblingNodesFilter::create(pagination: Pagination::fromLimitAndOffset(1, 0)), + if ($succeedingSiblingId) { + $variantSucceedingSibling = $variantSubgraph->findNodeById($succeedingSiblingId); + if ($variantSucceedingSibling) { + // a) happy path, the explicitly requested succeeding sibling also exists in this dimension space point + $interdimensionalSiblings[] = new InterdimensionalSibling( + $dimensionSpacePoint, + $variantSucceedingSibling->nodeAggregateId, ); - if (count($alternateSucceedingSiblings) > 0) { - $succeedingSibling = $alternateSucceedingSiblings->first(); + continue; + } + + // check the other siblings succeeding in the selected dimension space point + foreach ($alternativeSucceedingSiblingIds ?: [] as $alternativeSucceedingSiblingId) { + $alternativeVariantSucceedingSibling = $variantSubgraph->findNodeById($alternativeSucceedingSiblingId); + if (!$alternativeVariantSucceedingSibling) { + continue; } - } else { - $succeedingSibling = $this->resolveSucceedingSiblingFromOriginSiblings( - $nodeAggregate->nodeAggregateId, - $parentId, - $precedingSiblingId, - $succeedingSiblingId, - $contentSubgraph, - $originContentSubgraph + // b) one of the further succeeding sibling exists in this dimension space point + $interdimensionalSiblings[] = new InterdimensionalSibling( + $dimensionSpacePoint, + $alternativeVariantSucceedingSibling->nodeAggregateId, ); + continue 2; } } - if ($succeedingSibling) { - // for the event payload, we additionally need the parent of the succeeding sibling - $parentOfSucceedingSibling = $contentSubgraph->findParentNode($succeedingSibling->nodeAggregateId); - if ($parentOfSucceedingSibling === null) { - throw new \InvalidArgumentException( - 'Parent of succeeding sibling ' . $succeedingSibling->nodeAggregateId->value - . ' not found in subgraph ' . json_encode($contentSubgraph), - 1667817639 - ); + if ($precedingSiblingId) { + $variantPrecedingSiblingId = null; + $variantPrecedingSibling = $variantSubgraph->findNodeById($precedingSiblingId); + if ($variantPrecedingSibling) { + // c) happy path, the explicitly requested preceding sibling also exists in this dimension space point + $variantPrecedingSiblingId = $precedingSiblingId; + } elseif ($alternativePrecedingSiblingIds) { + // check the other siblings preceding in the selected dimension space point + foreach ($alternativePrecedingSiblingIds as $alternativePrecedingSiblingId) { + $alternativeVariantSucceedingSibling = $variantSubgraph->findNodeById($alternativePrecedingSiblingId); + if ($alternativeVariantSucceedingSibling) { + // d) one of the further preceding siblings exists in this dimension space point + $variantPrecedingSiblingId = $alternativePrecedingSiblingId; + break; + } + } } - $coverageNodeMoveMappings[] = CoverageNodeMoveMapping::createForNewSucceedingSibling( - $dimensionSpacePoint, - SucceedingSiblingNodeMoveDestination::create( - $succeedingSibling->nodeAggregateId, - $succeedingSibling->originDimensionSpacePoint, - $parentOfSucceedingSibling->nodeAggregateId, - $parentOfSucceedingSibling->originDimensionSpacePoint, - ) - ); - } else { - // preceding / succeeding siblings could not be resolved for a given covered DSP - // -> Fall back to resolving based on the parent - - if ($parentId === null) { - // if parent ID is not given, use the parent of the original node, because we want to move - // to the end of the sibling list. - $parentId = $contentSubgraph->findParentNode($nodeAggregate->nodeAggregateId)?->nodeAggregateId; - if ($parentId === null) { - throw new \InvalidArgumentException( - 'Parent ' . $parentId . ' not found in subgraph ' . json_encode($contentSubgraph), - 1667597013 - ); - } + if ($variantPrecedingSiblingId) { + $succeedingSibling = $variantSubgraph->findSucceedingSiblingNodes( + $variantPrecedingSiblingId, + FindSucceedingSiblingNodesFilter::create(pagination: Pagination::fromLimitAndOffset(1, 0)) + )->first(); + $interdimensionalSiblings[] = new InterdimensionalSibling( + $dimensionSpacePoint, + $succeedingSibling?->nodeAggregateId, + ); + continue; } - $coverageNodeMoveMappings[] = $this->resolveNewParentAssignments( - $contentStreamId, - $parentId, + } + + // e) fallback: if the set is to be completed, we add an empty sibling, otherwise we just don't + if ($completeSet) { + $interdimensionalSiblings[] = new InterdimensionalSibling( $dimensionSpacePoint, - $contentRepository + null, ); } } - return CoverageNodeMoveMappings::create(...$coverageNodeMoveMappings); + return new InterdimensionalSiblings(...$interdimensionalSiblings); } } diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Exception/NodeAggregateIsNoChild.php b/Neos.ContentRepository.Core/Classes/SharedModel/Exception/NodeAggregateIsNoChild.php new file mode 100644 index 00000000000..80807f60f97 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Exception/NodeAggregateIsNoChild.php @@ -0,0 +1,41 @@ +value + . '" is no child of node aggregate "' . $parentNodeAggregateId->value + . '" but was expected to be in dimension space point ' + . json_encode($dimensionSpacePoint, JSON_THROW_ON_ERROR), + 1713081351 + ); + } +} diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Exception/NodeAggregateIsNoSibling.php b/Neos.ContentRepository.Core/Classes/SharedModel/Exception/NodeAggregateIsNoSibling.php new file mode 100644 index 00000000000..9835d927baa --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Exception/NodeAggregateIsNoSibling.php @@ -0,0 +1,38 @@ +value + . '" is no sibling of node aggregate "' . $referenceNodeAggregateId->value + . '" but was expected to be in dimension space point ' + . json_encode($dimensionSpacePoint, JSON_THROW_ON_ERROR), + 1713081020 + ); + } +} diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php index a621f5309e2..9c699871490 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php @@ -15,15 +15,11 @@ use Neos\ContentRepository\Core\DimensionSpace\VariantType; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\EventStore\EventNormalizer; +use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSibling; use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSiblings; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite; use Neos\ContentRepository\Core\Feature\NodeModification\Event\NodePropertiesWereSet; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMapping; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMappings; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\OriginNodeMoveMapping; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\OriginNodeMoveMappings; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\SucceedingSiblingNodeMoveDestination; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; use Neos\ContentRepository\Core\Feature\NodeReferencing\Dto\SerializedNodeReferences; use Neos\ContentRepository\Core\Feature\NodeReferencing\Event\NodeReferencesWereSet; @@ -475,23 +471,13 @@ private function createNodeVariant(NodeAggregateId $nodeAggregateId, OriginDimen $this->exportEvent(new NodeAggregateWasMoved( $this->contentStreamId, $nodeAggregateId, - OriginNodeMoveMappings::fromArray([ - new OriginNodeMoveMapping( - $originDimensionSpacePoint, - CoverageNodeMoveMappings::create( - CoverageNodeMoveMapping::createForNewSucceedingSibling( - $originDimensionSpacePoint->toDimensionSpacePoint(), - SucceedingSiblingNodeMoveDestination::create( - $parentNodeAggregate->nodeAggregateId, - $variantSourceOriginDimensionSpacePoint, - - $nodeAggregate->getVariant($variantSourceOriginDimensionSpacePoint)->parentNodeAggregateId, - $nodeAggregate->getVariant($variantSourceOriginDimensionSpacePoint)->originDimensionSpacePoint - ) - ) - ) + $parentNodeAggregate->nodeAggregateId, + new InterdimensionalSiblings( + new InterdimensionalSibling( + $originDimensionSpacePoint->toDimensionSpacePoint(), + null ) - ]) + ) )); } } diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index fcdc1104af6..d0f0479a9ba 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -8,14 +8,11 @@ use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; +use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSibling; +use Neos\ContentRepository\Core\Feature\Common\InterdimensionalSiblings; use Neos\ContentRepository\Core\Feature\Common\NodeVariationInternals; use Neos\ContentRepository\Core\Feature\Common\TetheredNodeInternals; use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMapping; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMappings; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\OriginNodeMoveMapping; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\OriginNodeMoveMappings; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\SucceedingSiblingNodeMoveDestination; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeType; @@ -24,7 +21,6 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; -use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\EventStore\Model\EventStream\ExpectedVersion; @@ -166,8 +162,7 @@ function () use ($tetheredNodeAggregate) { . implode(', ', array_keys($actualTetheredChildNodes)), fn () => $this->reorderNodes( $nodeAggregate->contentStreamId, - $nodeAggregate->nodeAggregateId, - $originDimensionSpacePoint, + $nodeAggregate->getCoverageByOccupant($originDimensionSpacePoint), $actualTetheredChildNodes, array_keys($expectedTetheredNodes) ) @@ -224,8 +219,7 @@ protected function getPropertyConverter(): PropertyConverter */ private function reorderNodes( ContentStreamId $contentStreamId, - NodeAggregateId $parentNodeAggregateId, - DimensionSpace\OriginDimensionSpacePoint $originDimensionSpacePoint, + DimensionSpace\DimensionSpacePointSet $coverageByOrigin, array $actualTetheredChildNodes, array $expectedNodeOrdering ): EventsToPublish { @@ -235,33 +229,22 @@ private function reorderNodes( $succeedingSiblingNodeName = array_pop($expectedNodeOrdering); while ($nodeNameToMove = array_pop($expectedNodeOrdering)) { // let's move $nodeToMove before $succeedingNode. - /* @var $nodeToMove Node */ $nodeToMove = $actualTetheredChildNodes[$nodeNameToMove]; - /* @var $succeedingNode Node */ $succeedingNode = $actualTetheredChildNodes[$succeedingSiblingNodeName]; + $succeedingSiblingsForCoverage = []; + foreach ($coverageByOrigin as $coveredDimensionSpacePoint) { + $succeedingSiblingsForCoverage[] = new InterdimensionalSibling( + $coveredDimensionSpacePoint, + $succeedingNode->nodeAggregateId + ); + } + $events[] = new NodeAggregateWasMoved( $contentStreamId, $nodeToMove->nodeAggregateId, - OriginNodeMoveMappings::fromArray([ - new OriginNodeMoveMapping( - $nodeToMove->originDimensionSpacePoint, - CoverageNodeMoveMappings::create( - CoverageNodeMoveMapping::createForNewSucceedingSibling( - // TODO: I am not sure the next line is 100% correct. IMHO this must be the COVERED - // TODO: DimensionSpacePoint (though I am not sure whether we have that one now) - $nodeToMove->originDimensionSpacePoint->toDimensionSpacePoint(), - SucceedingSiblingNodeMoveDestination::create( - $succeedingNode->nodeAggregateId, - $succeedingNode->originDimensionSpacePoint, - // we only change the order, not the parent -> so we can simply use the parent here. - $parentNodeAggregateId, - $originDimensionSpacePoint - ) - ) - ) - ) - ]), + null, + new InterdimensionalSiblings(...$succeedingSiblingsForCoverage), ); // now, go one step left. diff --git a/Neos.Neos/Classes/FrontendRouting/CatchUpHook/RouterCacheHook.php b/Neos.Neos/Classes/FrontendRouting/CatchUpHook/RouterCacheHook.php index 3c82e38109e..3c57bc02c14 100644 --- a/Neos.Neos/Classes/FrontendRouting/CatchUpHook/RouterCacheHook.php +++ b/Neos.Neos/Classes/FrontendRouting/CatchUpHook/RouterCacheHook.php @@ -6,7 +6,6 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\NodeModification\Event\NodePropertiesWereSet; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMapping; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; use Neos\ContentRepository\Core\Feature\NodeRemoval\Event\NodeAggregateWasRemoved; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasTagged; @@ -140,21 +139,20 @@ private function onBeforeNodeAggregateWasMoved(NodeAggregateWasMoved $event): vo return; } - foreach ($event->nodeMoveMappings as $moveMapping) { - /* @var \Neos\ContentRepository\Core\Feature\NodeMove\Dto\OriginNodeMoveMapping $moveMapping */ - foreach ($moveMapping->newLocations as $newLocation) { - /* @var $newLocation CoverageNodeMoveMapping */ - $node = $this->findDocumentNodeInfoByIdAndDimensionSpacePoint($event->nodeAggregateId, $newLocation->coveredDimensionSpacePoint); - if (!$node) { - // node probably no document node, skip - continue; - } + foreach ($event->succeedingSiblingsForCoverage as $succeedingSiblingForCoverage) { + $node = $this->findDocumentNodeInfoByIdAndDimensionSpacePoint( + $event->nodeAggregateId, + $succeedingSiblingForCoverage->dimensionSpacePoint + ); + if (!$node) { + // node probably no document node, skip + continue; + } - $this->collectTagsToFlush($node); + $this->collectTagsToFlush($node); - $descendantsOfNode = $this->getState()->getDescendantsOfNode($node); - array_map($this->collectTagsToFlush(...), iterator_to_array($descendantsOfNode)); - } + $descendantsOfNode = $this->getState()->getDescendantsOfNode($node); + array_map($this->collectTagsToFlush(...), iterator_to_array($descendantsOfNode)); } } diff --git a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php index c6e503d57c8..886983c9f30 100644 --- a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php +++ b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathProjection.php @@ -15,9 +15,6 @@ use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Event\DimensionSpacePointWasMoved; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; use Neos\ContentRepository\Core\Feature\NodeModification\Event\NodePropertiesWereSet; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\CoverageNodeMoveMapping; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\ParentNodeMoveDestination; -use Neos\ContentRepository\Core\Feature\NodeMove\Dto\SucceedingSiblingNodeMoveDestination; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; use Neos\ContentRepository\Core\Feature\NodeRemoval\Event\NodeAggregateWasRemoved; use Neos\ContentRepository\Core\Feature\NodeTypeChange\Event\NodeAggregateTypeWasChanged; @@ -617,49 +614,36 @@ private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event): void return; } - foreach ($event->nodeMoveMappings as $moveMapping) { - /* @var \Neos\ContentRepository\Core\Feature\NodeMove\Dto\OriginNodeMoveMapping $moveMapping */ - foreach ($moveMapping->newLocations as $newLocation) { - /* @var $newLocation CoverageNodeMoveMapping */ - $node = $this->tryGetNode(fn () => $this->getState()->getByIdAndDimensionSpacePointHash( - $event->nodeAggregateId, - $newLocation->coveredDimensionSpacePoint->hash - )); - if (!$node) { - // node probably no document node, skip - continue; - } - - match ($newLocation->destination::class) { - SucceedingSiblingNodeMoveDestination::class => $this->moveNode( - /** @var SucceedingSiblingNodeMoveDestination $newLocation->destination */ - $node, - $newLocation->destination->parentNodeAggregateId, - $newLocation->destination->nodeAggregateId - ), - ParentNodeMoveDestination::class => $this->moveNode( - /** @var ParentNodeMoveDestination $newLocation->destination */ - $node, - $newLocation->destination->nodeAggregateId, - null - ), - }; - - $this->getState()->purgeCacheFor($node); + foreach ($event->succeedingSiblingsForCoverage as $succeedingSiblingForCoverage) { + $node = $this->tryGetNode(fn () => $this->getState()->getByIdAndDimensionSpacePointHash( + $event->nodeAggregateId, + $succeedingSiblingForCoverage->dimensionSpacePoint->hash + )); + if (!$node) { + // node probably no document node, skip + continue; } + + $this->moveNode( + $node, + $event->newParentNodeAggregateId, + $succeedingSiblingForCoverage->nodeAggregateId + ); + + $this->getState()->purgeCacheFor($node); } } private function moveNode( DocumentNodeInfo $node, - NodeAggregateId $newParentNodeAggregateId, + ?NodeAggregateId $newParentNodeAggregateId, ?NodeAggregateId $newSucceedingNodeAggregateId ): void { $this->disconnectNodeFromSiblings($node); - $this->connectNodeWithSiblings($node, $newParentNodeAggregateId, $newSucceedingNodeAggregateId); + $this->connectNodeWithSiblings($node, $newParentNodeAggregateId ?: $node->getParentNodeAggregateId(), $newSucceedingNodeAggregateId); - if ($newParentNodeAggregateId->equals($node->getParentNodeAggregateId())) { + if (!$newParentNodeAggregateId || $newParentNodeAggregateId->equals($node->getParentNodeAggregateId())) { return; } $newParentNode = $this->tryGetNode(fn () => $this->getState()->getByIdAndDimensionSpacePointHash( diff --git a/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php b/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php index fd67227a3de..6cbd8ffbb23 100644 --- a/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php +++ b/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php @@ -26,6 +26,7 @@ use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Event\DimensionSpacePointWasMoved; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; use Neos\ContentRepository\Core\Feature\NodeModification\Event\NodePropertiesWereSet; +use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; use Neos\ContentRepository\Core\Feature\NodeReferencing\Event\NodeReferencesWereSet; use Neos\ContentRepository\Core\Feature\NodeRemoval\Event\NodeAggregateWasRemoved; @@ -183,7 +184,7 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void { match ($event::class) { RootWorkspaceWasCreated::class => $this->whenRootWorkspaceWasCreated($event), - NodeAggregateWasMoved::class => $this->whenNodeAggregateWasMoved($event), + NodeAggregateWasMoved::class => $this->whenNodeAggregateWasMoved($event, $eventEnvelope), NodePropertiesWereSet::class => $this->whenNodePropertiesWereSet($event), NodeReferencesWereSet::class => $this->whenNodeReferencesWereSet($event), NodeAggregateWithNodeWasCreated::class => $this->whenNodeAggregateWithNodeWasCreated($event), @@ -230,18 +231,22 @@ private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): vo } } - private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event): void + private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event, EventEnvelope $eventEnvelope): void { - // WORKAROUND: we simply use the first MoveNodeMapping here to find the dimension space point - // @todo properly handle this - /* @var \Neos\ContentRepository\Core\Feature\NodeMove\Dto\OriginNodeMoveMapping[] $mapping */ - $mapping = iterator_to_array($event->nodeMoveMappings); - - $this->markAsMoved( - $event->getContentStreamId(), - $event->getNodeAggregateId(), - $mapping[0]->movedNodeOrigin - ); + // Changes reflect the editorial intention, not the effect as they are later published via commands + $metadata = $eventEnvelope->event->metadata?->value ?? []; + if (isset($metadata['commandPayload'])) { + $command = MoveNodeAggregate::fromArray($metadata['commandPayload']); + // WORKAROUND: we simply use the command's DSP here as the origin dimension space point. + // But this DSP is not necessarily occupied. + // @todo properly handle this by storing the necessary information in the projection + + $this->markAsMoved( + $event->getContentStreamId(), + $event->getNodeAggregateId(), + OriginDimensionSpacePoint::fromDimensionSpacePoint($command->dimensionSpacePoint) + ); + } } private function whenNodePropertiesWereSet(NodePropertiesWereSet $event): void From 13bcf438d2ebf644d6fbade81ef54ddd5b3d966c Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sun, 14 Apr 2024 21:58:14 +0200 Subject: [PATCH 025/214] Fix sibling assignments to pass tests <3 --- ...deAggregate_NoNewParent_Dimensions.feature | 972 +++++++++--------- .../Classes/Feature/NodeMove/NodeMove.php | 30 +- 2 files changed, 493 insertions(+), 509 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NoNewParent_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NoNewParent_Dimensions.feature index bf97db53f9a..906fe2c3ed7 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NoNewParent_Dimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NoNewParent_Dimensions.feature @@ -51,6 +51,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 9 events to be published on stream "ContentStream:cs-identifier" + And event at index 8 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "eldest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -110,6 +118,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "eldest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -170,6 +186,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -226,6 +250,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 9 events to be published on stream "ContentStream:cs-identifier" + And event at index 8 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -291,6 +323,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "younger-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -355,6 +395,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -383,11 +431,13 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + # The given succeeding sibling cannot be resolved and since younger-mc-nodeface isn't given as a preceding sibling, nothing is done | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have no succeeding siblings When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} @@ -411,6 +461,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 9 events to be published on stream "ContentStream:cs-identifier" + And event at index 8 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -472,6 +530,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -532,6 +598,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -588,6 +662,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 9 events to be published on stream "ContentStream:cs-identifier" + And event at index 8 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -653,6 +735,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -717,6 +807,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -744,11 +842,13 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have no preceding siblings + And I expect this node to have the following preceding siblings: + # The given preceding sibling cannot be resolved and since elder-mc-nodeface isn't given as a succeeding sibling, nothing is done + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} @@ -775,6 +875,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 9 events to be published on stream "ContentStream:cs-identifier" + And event at index 8 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "eldest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -836,6 +944,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -898,6 +1014,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -954,6 +1078,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 9 events to be published on stream "ContentStream:cs-identifier" + And event at index 8 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -962,8 +1094,8 @@ Feature: Move a node with content dimensions | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} @@ -973,9 +1105,9 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} @@ -985,9 +1117,9 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} @@ -998,8 +1130,8 @@ Feature: Move a node with content dimensions | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | Scenario: Move a node and its specialization variants before one of its siblings, which is not the first and does not exist in all variants @@ -1019,6 +1151,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "younger-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -1027,8 +1167,8 @@ Feature: Move a node with content dimensions | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} @@ -1038,9 +1178,9 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} @@ -1050,8 +1190,8 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} @@ -1062,8 +1202,8 @@ Feature: Move a node with content dimensions | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | Scenario: Move a node and its specialization variants before one of its siblings, which is the last and does not exist in all variants @@ -1075,24 +1215,32 @@ Feature: Move a node with content dimensions And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | "youngest-mc-nodeface" | - | relationDistributionStrategy | "gatherSpecializations" | + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "youngest-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} @@ -1111,41 +1259,51 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: + # The given succeeding sibling cannot be resolved and since elder-mc-nodeface isn't given as a preceding sibling, nothing is done | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have no succeeding siblings + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | Scenario: Move a node and its specialization variants after the last of its siblings When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | null | - | relationDistributionStrategy | "gatherSpecializations" | + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 9 events to be published on stream "ContentStream:cs-identifier" + And event at index 8 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -1177,9 +1335,9 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -1194,21 +1352,29 @@ Feature: Move a node with content dimensions And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | null | - | relationDistributionStrategy | "gatherSpecializations" | + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | null | + | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -1239,9 +1405,9 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -1256,24 +1422,32 @@ Feature: Move a node with content dimensions And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | - | relationDistributionStrategy | "gatherSpecializations" | + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} @@ -1302,34 +1476,42 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | Scenario: Move a node and its specialization variants after one of its siblings, which is not the last When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | - | relationDistributionStrategy | "gatherSpecializations" | + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 9 events to be published on stream "ContentStream:cs-identifier" + And event at index 8 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} @@ -1360,12 +1542,12 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | Scenario: Move a node and its specialization variants after one of its siblings, which is not the last and does not exist in all variants @@ -1377,24 +1559,32 @@ Feature: Move a node with content dimensions And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | - | relationDistributionStrategy | "gatherSpecializations" | + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} @@ -1424,12 +1614,12 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | Scenario: Move a node and its specialization variants after one of its siblings, which is the first and does not exist in all variants @@ -1441,20 +1631,28 @@ Feature: Move a node with content dimensions And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | - | relationDistributionStrategy | "gatherSpecializations" | + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | @@ -1476,11 +1674,13 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have no preceding siblings - And I expect this node to have the following succeeding siblings: + And I expect this node to have the following preceding siblings: + # The given preceding sibling cannot be resolved and since elder-mc-nodeface isn't given as a succeeding sibling, nothing is done | NodeDiscriminator | | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} @@ -1488,7 +1688,7 @@ Feature: Move a node with content dimensions And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | @@ -1499,14 +1699,22 @@ Feature: Move a node with content dimensions Scenario: Move a single node before the first of its siblings When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | - | relationDistributionStrategy | "scatter" | + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | + | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 9 events to be published on stream "ContentStream:cs-identifier" + And event at index 8 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -1533,9 +1741,9 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -1552,23 +1760,35 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - Scenario: Move a single node before the first of its siblings, which does not exist in all variants + # Scenario: Move a single node before the first of its siblings, which does not exist in all variants + # This scenario is invalid because the given succeeding sibling does not exist in the selected DSP. + # This constraint check is enforced by the command handler. + + Scenario: Move a single node before a siblings which is partially the first Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "eldest-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "source"} | - | nodeVariantSelectionStrategy | "allSpecializations" | + | Key | Value | + | nodeAggregateId | "eldest-mc-nodeface" | + | coveredDimensionSpacePoint | {"example": "source"} | + | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | - | relationDistributionStrategy | "scatter" | + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -1595,8 +1815,8 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -1614,23 +1834,24 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - Scenario: Move a single node before a siblings which is partially the first - Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "eldest-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "source"} | - | nodeVariantSelectionStrategy | "allSpecializations" | - And the graph projection is fully up to date - + Scenario: Move a single node before one of its siblings, which is not the first When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | - | relationDistributionStrategy | "scatter" | + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | null | + | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 9 events to be published on stream "ContentStream:cs-identifier" + And event at index 8 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -1646,143 +1867,25 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have no preceding siblings - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - Scenario: Move a single node before one of its siblings, which is not the first - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - Scenario: Move a single node before one of its siblings, which is not the first and does not exist in all variants - Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "elder-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "source"} | - | nodeVariantSelectionStrategy | "allSpecializations" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} @@ -1792,72 +1895,18 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - Scenario: Move a single node before one of its siblings, which is the last and does not exist in all variants - Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "youngest-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "source"} | - | nodeVariantSelectionStrategy | "allSpecializations" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newSucceedingSiblingNodeAggregateId | "youngest-mc-nodeface" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have no succeeding siblings + # Scenario: Move a single node before one of its siblings, which is not the first and does not exist in all variants + # This scenario is invalid because the given succeeding sibling does not exist in the selected DSP. + # This constraint check is enforced by the command handler. - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + # Scenario: Move a single node before one of its siblings, which is the last and does not exist in all variants + # This scenario is invalid because the given succeeding sibling does not exist in the selected DSP. + # This constraint check is enforced by the command handler. Scenario: Move a single node after the last of its siblings When the command MoveNodeAggregate is executed with payload: @@ -1866,16 +1915,24 @@ Feature: Move a node with content dimensions | dimensionSpacePoint | {"example": "source"} | | newParentNodeAggregateId | null | | newSucceedingSiblingNodeAggregateId | null | - | relationDistributionStrategy | "scatter" | + | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 9 events to be published on stream "ContentStream:cs-identifier" + And event at index 8 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -1896,9 +1953,9 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -1908,9 +1965,9 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -1920,7 +1977,7 @@ Feature: Move a node with content dimensions Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "youngest-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "source"} | + | coveredDimensionSpacePoint | {"example": "source"} | | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date @@ -1930,16 +1987,24 @@ Feature: Move a node with content dimensions | dimensionSpacePoint | {"example": "source"} | | newParentNodeAggregateId | null | | newSucceedingSiblingNodeAggregateId | null | - | relationDistributionStrategy | "scatter" | + | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -1949,19 +2014,19 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -1970,9 +2035,9 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -1982,7 +2047,7 @@ Feature: Move a node with content dimensions Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "youngest-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "source"} | + | coveredDimensionSpacePoint | {"example": "source"} | | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date @@ -1992,19 +2057,27 @@ Feature: Move a node with content dimensions | dimensionSpacePoint | {"example": "source"} | | newParentNodeAggregateId | null | | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | - | relationDistributionStrategy | "scatter" | + | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 10 events to be published on stream "ContentStream:cs-identifier" + And event at index 9 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} @@ -2021,9 +2094,9 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | @@ -2032,12 +2105,12 @@ Feature: Move a node with content dimensions And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | Scenario: Move a single node after one of its siblings, which is not the last @@ -2047,84 +2120,27 @@ Feature: Move a node with content dimensions | dimensionSpacePoint | {"example": "source"} | | newParentNodeAggregateId | null | | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | - | relationDistributionStrategy | "scatter" | + | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - Scenario: Move a single node after one of its siblings, which is not the last and does not exist in all variants - Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "younger-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "source"} | - | nodeVariantSelectionStrategy | "allSpecializations" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date + Then I expect exactly 9 events to be published on stream "ContentStream:cs-identifier" + And event at index 8 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"}] | When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} @@ -2132,6 +2148,7 @@ Feature: Move a node with content dimensions And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: @@ -2147,78 +2164,25 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - Scenario: Move a single node after one of its siblings, which is the first and does not exist in all variants - Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "eldest-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "source"} | - | nodeVariantSelectionStrategy | "allSpecializations" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | null | - | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have no succeeding siblings - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + # Scenario: Move a single node after one of its siblings, which is not the last and does not exist in all variants + # This scenario is invalid because the given succeeding sibling does not exist in the selected DSP. + # This constraint check is enforced by the command handler. - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} - And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + # Scenario: Move a single node after one of its siblings, which is the first and does not exist in all variants + # This scenario is invalid because the given succeeding sibling does not exist in the selected DSP. + # This constraint check is enforced by the command handler. diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index 15e173d89c0..d1b01efe4bc 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -200,9 +200,11 @@ private function handleMoveNodeAggregate( $contentStreamId, $command->dimensionSpacePoint, $affectedDimensionSpacePoints, + $command->nodeAggregateId, $command->newSucceedingSiblingNodeAggregateId, $command->newPrecedingSiblingNodeAggregateId, - $command->newParentNodeAggregateId !== null, + $command->newParentNodeAggregateId !== null + || $command->newSucceedingSiblingNodeAggregateId === null && $command->newPrecedingSiblingNodeAggregateId === null, $contentRepository ) ) @@ -241,12 +243,14 @@ private function resolveAffectedDimensionSpacePointSet( /** * @param bool $completeSet Whether unresolvable siblings should be added as null or not at all * True when a new parent is set, which will result of the node being added at the end + * True when no preceding sibling is given and the succeeding sibling is explicitly set to null, which will result of the node being added at the end * False when no new parent is set, which will result in the node not being moved */ private function resolveInterdimensionalSiblingsForMove( ContentStreamId $contentStreamId, DimensionSpacePoint $selectedDimensionSpacePoint, DimensionSpacePointSet $affectedDimensionSpacePoints, + NodeAggregateId $nodeAggregateId, ?NodeAggregateId $succeedingSiblingId, ?NodeAggregateId $precedingSiblingId, bool $completeSet, @@ -290,6 +294,10 @@ private function resolveInterdimensionalSiblingsForMove( // check the other siblings succeeding in the selected dimension space point foreach ($alternativeSucceedingSiblingIds ?: [] as $alternativeSucceedingSiblingId) { + // the node itself is no valid succeeding sibling + if ($alternativeSucceedingSiblingId->equals($nodeAggregateId)) { + continue; + } $alternativeVariantSucceedingSibling = $variantSubgraph->findNodeById($alternativeSucceedingSiblingId); if (!$alternativeVariantSucceedingSibling) { continue; @@ -312,6 +320,10 @@ private function resolveInterdimensionalSiblingsForMove( } elseif ($alternativePrecedingSiblingIds) { // check the other siblings preceding in the selected dimension space point foreach ($alternativePrecedingSiblingIds as $alternativePrecedingSiblingId) { + // the node itself is no valid preceding sibling + if ($alternativePrecedingSiblingId->equals($nodeAggregateId)) { + continue; + } $alternativeVariantSucceedingSibling = $variantSubgraph->findNodeById($alternativePrecedingSiblingId); if ($alternativeVariantSucceedingSibling) { // d) one of the further preceding siblings exists in this dimension space point @@ -322,13 +334,21 @@ private function resolveInterdimensionalSiblingsForMove( } if ($variantPrecedingSiblingId) { - $succeedingSibling = $variantSubgraph->findSucceedingSiblingNodes( + // we fetch two siblings because the first might be the to-be-moved node itself + $variantSucceedingSiblingIds = $variantSubgraph->findSucceedingSiblingNodes( $variantPrecedingSiblingId, - FindSucceedingSiblingNodesFilter::create(pagination: Pagination::fromLimitAndOffset(1, 0)) - )->first(); + FindSucceedingSiblingNodesFilter::create(pagination: Pagination::fromLimitAndOffset(2, 0)) + )->getIds(); + $relevantVariantSucceedingSiblingId = null; + foreach ($variantSucceedingSiblingIds as $variantSucceedingSiblingId) { + if (!$variantSucceedingSiblingId->equals($nodeAggregateId)) { + $relevantVariantSucceedingSiblingId = $variantSucceedingSiblingId; + break; + } + } $interdimensionalSiblings[] = new InterdimensionalSibling( $dimensionSpacePoint, - $succeedingSibling?->nodeAggregateId, + $relevantVariantSucceedingSiblingId, ); continue; } From dc4e7da2a9649b6554952a6fdc43e9f4882a7a42 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 15 Apr 2024 00:06:15 +0200 Subject: [PATCH 026/214] Extend and fix test cases for MoveNode to new parent --- ...NodeAggregate_NewParent_Dimensions.feature | 766 +++++++++--------- 1 file changed, 375 insertions(+), 391 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature index d3ffa539ad9..77a395b3f64 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature @@ -10,8 +10,8 @@ Feature: Move a node with content dimensions Background: Given using the following content dimensions: - | Identifier | Values | Generalizations | - | language | general, source, peer, gsw | gsw->source->general, peer->general | + | Identifier | Values | Generalizations | + | example | general, source, spec, peer | spec->source->general, peer->general | And using the following node types: """yaml 'Neos.ContentRepository.Testing:Document': [] @@ -56,6 +56,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 13 events to be published on stream "ContentStream:cs-identifier" + And event at index 12 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "eldest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -117,6 +125,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "eldest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -177,6 +193,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -233,6 +257,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 13 events to be published on stream "ContentStream:cs-identifier" + And event at index 12 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -296,6 +328,14 @@ Feature: Move a node with content dimensions | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "younger-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -360,6 +400,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -416,6 +464,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 13 events to be published on stream "ContentStream:cs-identifier" + And event at index 12 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -477,6 +533,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -537,6 +601,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId":"youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -593,6 +665,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 13 events to be published on stream "ContentStream:cs-identifier" + And event at index 12 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId":"youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -658,6 +738,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId":"youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -722,6 +810,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherAll" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId":"elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId":"elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -749,12 +845,13 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have no preceding siblings - And I expect this node to have the following succeeding siblings: + # The given preceding sibling cannot be resolved and since elder-mc-nodeface isn't given as a succeeding sibling, the node is moved at the end + And I expect this node to have the following preceding siblings: | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} @@ -770,7 +867,7 @@ Feature: Move a node with content dimensions # Test cases for the gatherSpecializations strategy - Scenario: Move a complete node aggregate to a new parent before the first of its new siblings + Scenario: Move a node and its specializations to a new parent before the first of its new siblings When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | @@ -780,6 +877,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 13 events to be published on stream "ContentStream:cs-identifier" + And event at index 12 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "eldest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} @@ -814,7 +919,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -822,7 +927,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent before the first of its new siblings - which does not exist in all variants + Scenario: Move a node and its specializations to a new parent before the first of its new siblings - which does not exist in all variants Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "eldest-mc-nodeface" | @@ -839,9 +944,17 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -872,7 +985,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -880,7 +993,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent before a siblings which is partially the first + Scenario: Move a node and its specializations to a new parent before a siblings which is partially the first Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "eldest-mc-nodeface" | @@ -897,9 +1010,17 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -931,7 +1052,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -939,19 +1060,27 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent before one of its new siblings, which is not the first + Scenario: Move a node and its specializations to a new parent before one of its new siblings, which is not the first When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "general"} | + | dimensionSpacePoint | {"example": "source"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 13 events to be published on stream "ContentStream:cs-identifier" + And event at index 12 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -985,7 +1114,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -993,7 +1122,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent before one of its siblings, which is not the first and does not exist in all variants + Scenario: Move a node and its specializations to a new parent before one of its siblings, which is not the first and does not exist in all variants Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "elder-mc-nodeface" | @@ -1004,13 +1133,23 @@ Feature: Move a node with content dimensions When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "younger-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1043,7 +1182,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1051,7 +1190,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent before one of its siblings, which is the last and does not exist in all variants + Scenario: Move a node and its specializations to a new parent before one of its siblings, which is the last and does not exist in all variants Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "youngest-mc-nodeface" | @@ -1068,9 +1207,17 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1102,7 +1249,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1110,7 +1257,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent after the last of its siblings + Scenario: Move a node and its specializations to a new parent after the last of its siblings When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | @@ -1120,9 +1267,17 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 13 events to be published on stream "ContentStream:cs-identifier" + And event at index 12 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1154,7 +1309,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1162,7 +1317,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent after the last of its siblings, which does not exist in all variants + Scenario: Move a node and its specializations to a new parent after the last of its siblings, which does not exist in all variants Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "youngest-mc-nodeface" | @@ -1179,9 +1334,17 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1212,7 +1375,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1220,7 +1383,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is partially the last + Scenario: Move a node and its specializations to a new parent after one of its siblings, which is partially the last Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "youngest-mc-nodeface" | @@ -1237,9 +1400,17 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1271,7 +1442,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1279,7 +1450,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is not the last + Scenario: Move a node and its specializations to a new parent after one of its siblings, which is not the last When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | @@ -1289,9 +1460,17 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 13 events to be published on stream "ContentStream:cs-identifier" + And event at index 12 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1325,7 +1504,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1333,7 +1512,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is not the last and does not exist in all variants + Scenario: Move a node and its specializations to a new parent after one of its siblings, which is not the last and does not exist in all variants Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "younger-mc-nodeface" | @@ -1350,9 +1529,17 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1385,7 +1572,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1393,7 +1580,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is the first and does not exist in all variants + Scenario: Move a node and its specializations to a new parent after one of its siblings, which is the first and does not exist in all variants Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "eldest-mc-nodeface" | @@ -1410,9 +1597,17 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1435,16 +1630,17 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have no preceding siblings - And I expect this node to have the following succeeding siblings: + And I expect this node to have the following preceding siblings: + # The given preceding sibling cannot be resolved and since elder-mc-nodeface isn't given as a succeeding sibling, the node is moved to the end | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1454,7 +1650,7 @@ Feature: Move a node with content dimensions # Test cases for the scatter strategy - Scenario: Move a complete node aggregate to a new parent before the first of its new siblings + Scenario: Move a single node to a new parent before the first of its new siblings When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | @@ -1464,6 +1660,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 13 events to be published on stream "ContentStream:cs-identifier" + And event at index 12 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"eldest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} @@ -1487,7 +1691,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1497,55 +1701,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - - Scenario: Move a complete node aggregate to a new parent before the first of its new siblings - which does not exist in all variants - Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "eldest-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "spec"} | - | nodeVariantSelectionStrategy | "allSpecializations" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | "sir-david-nodenborough" | - | newSucceedingSiblingNodeAggregateId | "eldest-mc-nodeface" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have no preceding siblings - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1553,17 +1709,11 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + # Scenario: Move a single node to a new parent before the first of its new siblings - which does not exist in all variants + # This scenario is invalid because the given succeeding sibling does not exist in the selected DSP. + # This constraint check is enforced by the command handler. - Scenario: Move a complete node aggregate to a new parent before a siblings which is partially the first + Scenario: Move a single node to a new parent before a siblings which is partially the first Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "eldest-mc-nodeface" | @@ -1574,15 +1724,23 @@ Feature: Move a node with content dimensions When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | + | dimensionSpacePoint | {"example": "spec"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1591,50 +1749,8 @@ Feature: Move a node with content dimensions | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - - Scenario: Move a complete node aggregate to a new parent before one of its new siblings, which is not the first - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "general"} | - | newParentNodeAggregateId | "sir-david-nodenborough" | - | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1642,31 +1758,19 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + And I expect this node to have no preceding siblings And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1674,24 +1778,27 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent before one of its siblings, which is not the first and does not exist in all variants - Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "elder-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "spec"} | - | nodeVariantSelectionStrategy | "allSpecializations" | - And the graph projection is fully up to date - + Scenario: Move a single node to a new parent before one of its new siblings, which is not the first When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newSucceedingSiblingNodeAggregateId | "elder-mc-nodeface" | + | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 13 events to be published on stream "ContentStream:cs-identifier" + And event at index 12 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"elder-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1713,7 +1820,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1723,34 +1830,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - - Scenario: Move a complete node aggregate to a new parent before one of its siblings, which is the last and does not exist in all variants - Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "youngest-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "spec"} | - | nodeVariantSelectionStrategy | "allSpecializations" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | "sir-david-nodenborough" | - | newSucceedingSiblingNodeAggregateId | "youngest-mc-nodeface" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1758,39 +1838,15 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + # Scenario: Move a single node to a new parent before one of its siblings, which is not the first and does not exist in all variants + # This scenario is invalid because the given succeeding sibling does not exist in the selected DSP. + # This constraint check is enforced by the command handler. - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + # Scenario: Move a single node to a new parent before one of its siblings, which is the last and does not exist in all variants + # This scenario is invalid because the given succeeding sibling does not exist in the selected DSP. + # This constraint check is enforced by the command handler. - Scenario: Move a complete node aggregate to a new parent after the last of its siblings + Scenario: Move a single node to a new parent after the last of its siblings When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | @@ -1800,9 +1856,17 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 13 events to be published on stream "ContentStream:cs-identifier" + And event at index 12 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1823,7 +1887,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1833,7 +1897,7 @@ Feature: Move a node with content dimensions When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1841,7 +1905,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent after the last of its siblings, which does not exist in all variants + Scenario: Move a single node to a new parent after the last of its siblings, which does not exist in all variants Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "youngest-mc-nodeface" | @@ -1852,15 +1916,23 @@ Feature: Move a node with content dimensions When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | + | dimensionSpacePoint | {"example": "spec"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newSucceedingSiblingNodeAggregateId | null | | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1869,29 +1941,28 @@ Feature: Move a node with content dimensions | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1899,7 +1970,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is partially the last + Scenario: Move a single node to a new parent after one of its siblings, which is partially the last Given the command RemoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "youngest-mc-nodeface" | @@ -1910,15 +1981,23 @@ Feature: Move a node with content dimensions When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | + | dimensionSpacePoint | {"example": "spec"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1927,50 +2006,8 @@ Feature: Move a node with content dimensions | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - - Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is not the last - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | "sir-david-nodenborough" | - | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -1978,7 +2015,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1986,23 +2023,11 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;elder-mc-nodeface;{"example": "general"} | | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + And I expect this node to have no succeeding siblings When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -2010,26 +2035,27 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is not the last and does not exist in all variants - Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "younger-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "spec"} | - | nodeVariantSelectionStrategy | "allSpecializations" | - And the graph projection is fully up to date - + Scenario: Move a single node to a new parent after one of its siblings, which is not the last When the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | + | dimensionSpacePoint | {"example": "spec"} | | newParentNodeAggregateId | "sir-david-nodenborough" | | newPrecedingSiblingNodeAggregateId | "younger-mc-nodeface" | | relationDistributionStrategy | "scatter" | And the graph projection is fully up to date + Then I expect exactly 13 events to be published on stream "ContentStream:cs-identifier" + And event at index 12 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"youngest-mc-nodeface"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -2038,57 +2064,8 @@ Feature: Move a node with content dimensions | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - - Scenario: Move a complete node aggregate to a new parent after one of its siblings, which is the first and does not exist in all variants - Given the command RemoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "eldest-mc-nodeface" | - | coveredDimensionSpacePoint | {"example": "spec"} | - | nodeVariantSelectionStrategy | "allSpecializations" | - And the graph projection is fully up to date - - When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | "sir-david-nodenborough" | - | newPrecedingSiblingNodeAggregateId | "eldest-mc-nodeface" | - | relationDistributionStrategy | "scatter" | - And the graph projection is fully up to date - - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -2096,31 +2073,21 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;eldest-mc-nodeface;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;younger-mc-nodeface;{"example": "general"} | + | cs-identifier;elder-mc-nodeface;{"example": "general"} | + | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | - | cs-identifier;elder-mc-nodeface;{"example": "general"} | - | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} - And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | - And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} - And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | | cs-identifier;source-elder-mc-nodeface;{"example": "general"} | @@ -2128,6 +2095,14 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | + # Scenario: Move a single node to a new parent after one of its siblings, which is not the last and does not exist in all variants + # This scenario is invalid because the given succeeding sibling does not exist in the selected DSP. + # This constraint check is enforced by the command handler. + + # Scenario: Move a single node to a new parent after one of its siblings, which is the first and does not exist in all variants + # This scenario is invalid because the given succeeding sibling does not exist in the selected DSP. + # This constraint check is enforced by the command handler. + # Other test cases Scenario: Move a node that has no name @@ -2143,6 +2118,15 @@ Feature: Move a node with content dimensions | nodeAggregateId | "nody-mc-nodeface-ii" | | newParentNodeAggregateId | "lady-eleonode-rootford" | | relationDistributionStrategy | "scatter" | + + Then I expect exactly 14 events to be published on stream "ContentStream:cs-identifier" + And event at index 13 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface-ii" | + | newParentNodeAggregateId | "lady-eleonode-rootford" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId":null}] | + And the graph projection is fully up to date When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface-ii" to lead to node cs-identifier;nody-mc-nodeface-ii;{"example": "general"} From c013cc36b2189204e7a109de54ac895638219da5 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 15 Apr 2024 21:47:22 +0200 Subject: [PATCH 027/214] Properly validate siblings --- ...oveNodeAggregate_ScatteredChildren.feature | 107 +++++++++++++----- .../Classes/Feature/NodeMove/NodeMove.php | 20 +++- 2 files changed, 95 insertions(+), 32 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ScatteredChildren.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ScatteredChildren.feature index e8c00aec5fe..151909239af 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ScatteredChildren.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ScatteredChildren.feature @@ -52,14 +52,22 @@ Feature: Move a node with content dimensions And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload: - | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | - | dimensionSpacePoint | {"example": "source"} | - | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | - | newSucceedingSiblingNodeAggregateIde | "bustling-destinode" | - | relationDistributionStrategy | "gatherSpecializations" | + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "source"} | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | newSucceedingSiblingNodeAggregateId | "bustling-destinode" | + | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 15 events to be published on stream "ContentStream:cs-identifier" + And event at index 14 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"bustling-destinode"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"younger-destinode"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -74,8 +82,9 @@ Feature: Move a node with content dimensions Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-destinode;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-destinode;{"example": "general"} | + | cs-identifier;nodimus-mediocre;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;bustling-destinode;{"example": "general"} | @@ -85,8 +94,9 @@ Feature: Move a node with content dimensions Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-destinode;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-destinode;{"example": "general"} | + | cs-identifier;nodimus-mediocre;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-destinode;{"example": "general"} | @@ -119,6 +129,14 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 15 events to be published on stream "ContentStream:cs-identifier" + And event at index 14 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "bustling-destinode" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} @@ -165,21 +183,41 @@ Feature: Move a node with content dimensions | Key | Value | | nodeAggregateId | "bustling-destinode" | | dimensionSpacePoint | {"example": "source"} | - | newSucceedingSiblingNodeAggregateId | "younger-destinode" | + | newSucceedingSiblingNodeAggregateId | "elder-destinode" | | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 15 events to be published on stream "ContentStream:cs-identifier" + And event at index 14 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "bustling-destinode" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"elder-destinode"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-destinode;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-destinode;{"example": "general"} | + | cs-identifier;nodimus-mediocre;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-destinode;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;nodimus-mediocre;{"example": "general"} | + And I expect this node to have the following succeeding siblings: + | NodeDiscriminator | + | cs-identifier;elder-destinode;{"example": "general"} | + | cs-identifier;younger-destinode;{"example": "general"} | + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/esquire-child/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;nodimus-mediocre;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -189,23 +227,19 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;younger-child-destinode;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/esquire-child/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} - And I expect this node to be a child of node cs-identifier;nodimus-mediocre;{"example": "general"} - And I expect this node to have no preceding siblings - And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-destinode;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;elder-destinode;{"example": "general"} | + | cs-identifier;nodimus-mediocre;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;younger-destinode;{"example": "general"} | Scenario: Scatter a node aggregate by moving a specialization variant to a different parent. Then let a sibling variant follow suit and move the sibling before the node in both variants. + # We expect to be the node to be the sibling's succeeding sibling in both variants across parents Given the command MoveNodeAggregate is executed with payload: | Key | Value | | nodeAggregateId | "bustling-destinode" | @@ -231,25 +265,35 @@ Feature: Move a node with content dimensions | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date + Then I expect exactly 16 events to be published on stream "ContentStream:cs-identifier" + And event at index 15 is of type "NodeAggregateWasMoved" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "elder-destinode" | + | newParentNodeAggregateId | null | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"bustling-destinode"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"bustling-destinode"}] | + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "elder-destinode" and node path "esquire/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} - And I expect this node to have no preceding siblings + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;nodimus-mediocre;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;bustling-destinode;{"example": "general"} | | cs-identifier;younger-destinode;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "elder-destinode" and node path "esquire/esquire-child/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} - And I expect this node to be a child of node cs-identifier;nodimus-mediocre;{"example": "general"} + Then I expect node aggregate identifier "elder-destinode" and node path "esquire/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} + And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: - | NodeDiscriminator | - | cs-identifier;elder-child-destinode;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;nodimus-mediocre;{"example": "general"} | And I expect this node to have the following succeeding siblings: - | NodeDiscriminator | - | cs-identifier;bustling-destinode;{"example": "general"} | - | cs-identifier;younger-child-destinode;{"example": "general"} | + | NodeDiscriminator | + | cs-identifier;bustling-destinode;{"example": "general"} | + | cs-identifier;younger-destinode;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "elder-destinode" and node path "esquire/esquire-child/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} @@ -259,12 +303,15 @@ Feature: Move a node with content dimensions | cs-identifier;elder-child-destinode;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | + | cs-identifier;bustling-destinode;{"example": "general"} | | cs-identifier;younger-child-destinode;{"example": "general"} | When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "elder-destinode" and node path "esquire/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} - And I expect this node to have no preceding siblings + And I expect this node to have the following preceding siblings: + | NodeDiscriminator | + | cs-identifier;nodimus-mediocre;{"example": "general"} | And I expect this node to have the following succeeding siblings: | NodeDiscriminator | | cs-identifier;bustling-destinode;{"example": "general"} | diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index d1b01efe4bc..52cbde78625 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -201,6 +201,7 @@ private function handleMoveNodeAggregate( $command->dimensionSpacePoint, $affectedDimensionSpacePoints, $command->nodeAggregateId, + $command->newParentNodeAggregateId, $command->newSucceedingSiblingNodeAggregateId, $command->newPrecedingSiblingNodeAggregateId, $command->newParentNodeAggregateId !== null @@ -241,6 +242,8 @@ private function resolveAffectedDimensionSpacePointSet( } /** + * @param ?NodeAggregateId $parentNodeAggregateId the parent node aggregate ID to validate variant siblings against. + * If no new parent is given, the siblings are validated against the parent of the to-be-moved node in the respective dimension space point. * @param bool $completeSet Whether unresolvable siblings should be added as null or not at all * True when a new parent is set, which will result of the node being added at the end * True when no preceding sibling is given and the succeeding sibling is explicitly set to null, which will result of the node being added at the end @@ -251,6 +254,7 @@ private function resolveInterdimensionalSiblingsForMove( DimensionSpacePoint $selectedDimensionSpacePoint, DimensionSpacePointSet $affectedDimensionSpacePoints, NodeAggregateId $nodeAggregateId, + ?NodeAggregateId $parentNodeAggregateId, ?NodeAggregateId $succeedingSiblingId, ?NodeAggregateId $precedingSiblingId, bool $completeSet, @@ -283,7 +287,9 @@ private function resolveInterdimensionalSiblingsForMove( ); if ($succeedingSiblingId) { $variantSucceedingSibling = $variantSubgraph->findNodeById($succeedingSiblingId); - if ($variantSucceedingSibling) { + $variantParentId = $parentNodeAggregateId ?: $variantSubgraph->findParentNode($nodeAggregateId)?->nodeAggregateId; + $siblingParent = $variantSubgraph->findParentNode($succeedingSiblingId); + if ($variantSucceedingSibling && $siblingParent && $variantParentId?->equals($siblingParent->nodeAggregateId)) { // a) happy path, the explicitly requested succeeding sibling also exists in this dimension space point $interdimensionalSiblings[] = new InterdimensionalSibling( $dimensionSpacePoint, @@ -302,6 +308,10 @@ private function resolveInterdimensionalSiblingsForMove( if (!$alternativeVariantSucceedingSibling) { continue; } + $siblingParent = $variantSubgraph->findParentNode($alternativeSucceedingSiblingId); + if (!$siblingParent || !$variantParentId?->equals($siblingParent->nodeAggregateId)) { + continue; + } // b) one of the further succeeding sibling exists in this dimension space point $interdimensionalSiblings[] = new InterdimensionalSibling( $dimensionSpacePoint, @@ -314,7 +324,9 @@ private function resolveInterdimensionalSiblingsForMove( if ($precedingSiblingId) { $variantPrecedingSiblingId = null; $variantPrecedingSibling = $variantSubgraph->findNodeById($precedingSiblingId); - if ($variantPrecedingSibling) { + $variantParentId = $parentNodeAggregateId ?: $variantSubgraph->findParentNode($nodeAggregateId)?->nodeAggregateId; + $siblingParent = $variantSubgraph->findParentNode($precedingSiblingId); + if ($variantPrecedingSibling && $siblingParent && $variantParentId->equals($siblingParent->nodeAggregateId)) { // c) happy path, the explicitly requested preceding sibling also exists in this dimension space point $variantPrecedingSiblingId = $precedingSiblingId; } elseif ($alternativePrecedingSiblingIds) { @@ -324,6 +336,10 @@ private function resolveInterdimensionalSiblingsForMove( if ($alternativePrecedingSiblingId->equals($nodeAggregateId)) { continue; } + $siblingParent = $variantSubgraph->findParentNode($alternativePrecedingSiblingId); + if (!$siblingParent || !$variantParentId->equals($siblingParent->nodeAggregateId)) { + continue; + } $alternativeVariantSucceedingSibling = $variantSubgraph->findNodeById($alternativePrecedingSiblingId); if ($alternativeVariantSucceedingSibling) { // d) one of the further preceding siblings exists in this dimension space point From 0bbc4ac40a894c646bf8f3f2d5d72f56a01f4d4d Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 15 Apr 2024 21:47:38 +0200 Subject: [PATCH 028/214] Fix NodeMove/SubtreeTags test suite --- .../05-MoveNodeAggregate_SubtreeTags.feature | 196 +++++++++--------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature index 2c6641c53b4..f38fdb6a08d 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature @@ -62,7 +62,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -95,7 +95,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -123,7 +123,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -156,7 +156,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -186,7 +186,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -197,7 +197,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" @@ -208,7 +208,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" @@ -219,7 +219,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -247,7 +247,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -258,7 +258,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -269,7 +269,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" @@ -280,7 +280,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -311,7 +311,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -321,30 +321,30 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -373,7 +373,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -383,30 +383,30 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -445,7 +445,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -455,30 +455,30 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag1" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag1" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -515,7 +515,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -525,30 +525,30 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag1" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -585,7 +585,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -595,30 +595,30 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag1" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -655,7 +655,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -665,30 +665,30 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag1" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -725,7 +725,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -735,30 +735,30 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag2" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag2" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -795,7 +795,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -805,30 +805,30 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag2" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -865,7 +865,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -875,30 +875,30 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag2" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag2" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -935,7 +935,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -945,30 +945,30 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag2" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -1228,7 +1228,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -1239,7 +1239,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag1" @@ -1250,7 +1250,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -1379,7 +1379,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag2" @@ -1427,7 +1427,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -1437,30 +1437,30 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag2" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "tag1" And I expect this node to be tagged with "tag2" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -1571,30 +1571,30 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -1633,23 +1633,23 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} - Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" - And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} + And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -1695,7 +1695,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -1706,7 +1706,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;nodimus-prime,{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" @@ -1728,7 +1728,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -1846,7 +1846,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" @@ -1857,7 +1857,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag1" @@ -2078,7 +2078,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-devid-nodenborough;{"example":"general"} + And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "" @@ -2629,7 +2629,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} + And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to tag with "" And I expect this node to be tagged with "tag2" From a6e9b1d76a158368cf18a45e6772358aca4f21cb Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 15 Apr 2024 21:51:22 +0200 Subject: [PATCH 029/214] Expect proper exception --- .../08-NodeMove/06-AdditionalConstraintChecks.feature | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature index a3ecd838bde..a401fe2a00d 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature @@ -60,20 +60,20 @@ Feature: Additional constraint checks after move node capabilities are introduce | nodeTypeName | "Neos.ContentRepository.Testing:Document" | | parentNodeAggregateId | "sir-nodeward-nodington-iii" | | nodeName | "document" | - Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + Then the last command should have thrown an exception of type "NodeNameIsAlreadyOccupied" When the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | | nodeTypeName | "Neos.ContentRepository.Testing:Document" | | parentNodeAggregateId | "lady-abigail-nodenborough" | | nodeName | "document" | - Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + Then the last command should have thrown an exception of type "NodeNameIsAlreadyOccupied" When the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | | nodeTypeName | "Neos.ContentRepository.Testing:Document" | | parentNodeAggregateId | "general-nodesworth" | | nodeName | "document" | - Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + Then the last command should have thrown an exception of type "NodeNameIsAlreadyOccupied" From 4961034ed297a3133ba04a3854e85ba01e7b11c2 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 15 Apr 2024 21:51:32 +0200 Subject: [PATCH 030/214] Adjust zero-dimensional test cases --- ...MoveNodeAggregateWithoutDimensions.feature | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/07-MoveNodeAggregateWithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/07-MoveNodeAggregateWithoutDimensions.feature index 048a6738ceb..42efb17c2fa 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/07-MoveNodeAggregateWithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/07-MoveNodeAggregateWithoutDimensions.feature @@ -26,9 +26,9 @@ Feature: Move a node without content dimensions And the graph projection is fully up to date And I am in the active content stream of workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-eleonode-rootford" | - | nodeTypeName | "Neos.ContentRepository:Root" | + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | And the event NodeAggregateWithNodeWasCreated was published with payload: | Key | Value | | contentStreamId | "cs-identifier" | @@ -151,10 +151,11 @@ Feature: Move a node without content dimensions Then I expect exactly 7 events to be published on stream "ContentStream:cs-identifier" And event at index 6 is of type "NodeAggregateWasMoved" with payload: - | Key | Expected | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | nodeMoveMappings | [{"movedNodeOrigin":[],"newLocations":[{"coveredDimensionSpacePoint": [], "newParent": {"nodeAggregateId":"sir-nodeward-nodington-iii","originDimensionSpacePoint":[]}}]}] | + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "sir-david-nodenborough" | + | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint": [],"nodeAggregateId":null}] | When the graph projection is fully up to date Then I expect the graph projection to consist of exactly 5 nodes @@ -197,10 +198,11 @@ Feature: Move a node without content dimensions | newSucceedingSiblingNodeAggregateId | "sir-nodeward-nodington-iii" | Then I expect exactly 6 events to be published on stream "ContentStream:cs-identifier" And event at index 5 is of type "NodeAggregateWasMoved" with payload: - | Key | Expected | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nody-mc-nodeface" | - | nodeMoveMappings | [{"movedNodeOrigin":[],"newLocations":[{"coveredDimensionSpacePoint": [],"newSucceedingSibling":{"nodeAggregateId":"sir-nodeward-nodington-iii","originDimensionSpacePoint":[], "parentNodeAggregateId": "lady-eleonode-rootford", "parentOriginDimensionSpacePoint": []}}]}] | + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "lady-eleonode-rootford" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint": [],"nodeAggregateId":"sir-nodeward-nodington-iii"}] | When the graph projection is fully up to date Then I expect the graph projection to consist of exactly 4 nodes From 38051db0fa3c2a8afb83eacf67b922cae4f7c482 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 15 Apr 2024 21:54:38 +0200 Subject: [PATCH 031/214] Pacify linter --- .../Classes/Feature/NodeMove/NodeMove.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index 52cbde78625..3aa951e27d3 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -326,7 +326,7 @@ private function resolveInterdimensionalSiblingsForMove( $variantPrecedingSibling = $variantSubgraph->findNodeById($precedingSiblingId); $variantParentId = $parentNodeAggregateId ?: $variantSubgraph->findParentNode($nodeAggregateId)?->nodeAggregateId; $siblingParent = $variantSubgraph->findParentNode($precedingSiblingId); - if ($variantPrecedingSibling && $siblingParent && $variantParentId->equals($siblingParent->nodeAggregateId)) { + if ($variantPrecedingSibling && $siblingParent && $variantParentId?->equals($siblingParent->nodeAggregateId)) { // c) happy path, the explicitly requested preceding sibling also exists in this dimension space point $variantPrecedingSiblingId = $precedingSiblingId; } elseif ($alternativePrecedingSiblingIds) { @@ -337,7 +337,7 @@ private function resolveInterdimensionalSiblingsForMove( continue; } $siblingParent = $variantSubgraph->findParentNode($alternativePrecedingSiblingId); - if (!$siblingParent || !$variantParentId->equals($siblingParent->nodeAggregateId)) { + if (!$siblingParent || !$variantParentId?->equals($siblingParent->nodeAggregateId)) { continue; } $alternativeVariantSucceedingSibling = $variantSubgraph->findNodeById($alternativePrecedingSiblingId); From e6e67e62494fc19a6f9364ca5c3e62f5cd766c88 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 15 Apr 2024 22:21:09 +0200 Subject: [PATCH 032/214] Adjust other projection test cases --- .../Projection/SiblingPositions.feature | 1 - ...AreConnectedToARootNodePerSubgraph.feature | 9 ++--- .../Tests/Behavior/Features/Variants.feature | 34 +++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/SiblingPositions.feature b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/SiblingPositions.feature index f8a1f8d864f..704e7c58933 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/SiblingPositions.feature +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/SiblingPositions.feature @@ -97,7 +97,6 @@ Feature: Sibling positions are properly resolved Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: | Name | NodeDiscriminator | - | document | cs-identifier;sir-david-nodenborough;{"example": "general"} | | esquire | cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} | | nodington-i | cs-identifier;lady-nodette-nodington-i;{"example": "general"} | | nodington-ii | cs-identifier;lady-nodette-nodington-ii;{"example": "general"} | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature index 9dd34c4676b..d9c1c1604fc 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesAreConnectedToARootNodePerSubgraph.feature @@ -52,10 +52,11 @@ Feature: Run projection integrity violation detection regarding root connection | nodeAggregateClassification | "regular" | And the graph projection is fully up to date And the event NodeAggregateWasMoved was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | nodeMoveMappings | [{"movedNodeOrigin":{"language":"de"},"newLocations":[{"coveredDimensionSpacePoint": {"language":"de"},"newParent":{"nodeAggregateId":"nody-mc-nodeface","originDimensionSpacePoint":{"language":"de"}}}, {"coveredDimensionSpacePoint": {"language":"gsw"},"newParent":{"nodeAggregateId":"nody-mc-nodeface","originDimensionSpacePoint":{"language":"de"}}}]}] | + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "sir-david-nodenborough" | + | newParentNodeAggregateId | "nody-mc-nodeface" | + | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"language":"de"},"nodeAggregateId": null},{"dimensionSpacePoint":{"language":"gsw"},"nodeAggregateId": null}] | And the graph projection is fully up to date And I run integrity violation detection # one error per subgraph diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Variants.feature b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Variants.feature index 00ed5bcfe98..693b379ce3b 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Variants.feature +++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Features/Variants.feature @@ -73,14 +73,14 @@ Feature: Migrating nodes with content dimensions | a1 | /sites/site/b/a1 | Some.Package:Thing | {"language": ["ch"]} | And I run the event migration Then I expect the following events to be exported - | Type | Payload | - | RootNodeAggregateWithNodeWasCreated | {} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site", "parentNodeAggregateId": "sites"} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a", "parentNodeAggregateId": "site"} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a1", "parentNodeAggregateId": "a", "originDimensionSpacePoint": {"language": "de"}, "succeedingSiblingsForCoverage": [{"dimensionSpacePoint":{"language": "de"},"nodeAggregateId":null},{"dimensionSpacePoint":{"language": "ch"},"nodeAggregateId":null}]} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "b", "parentNodeAggregateId": "site"} | - | NodeSpecializationVariantWasCreated | {"nodeAggregateId": "a1", "sourceOrigin": {"language": "de"}, "specializationOrigin": {"language": "ch"}, "specializationSiblings": [{"dimensionSpacePoint":{"language": "ch"},"nodeAggregateId":null}]} | - | NodeAggregateWasMoved | {"nodeAggregateId": "a1", "nodeMoveMappings": [{"movedNodeOrigin":{"language":"ch"},"newLocations":[{"coveredDimensionSpacePoint":{"language":"ch"},"newSucceedingSibling":{"nodeAggregateId":"b","originDimensionSpacePoint":{"language":"de"},"parentNodeAggregateId":"a","parentOriginDimensionSpacePoint":{"language":"de"}}}]}]} | + | Type | Payload | + | RootNodeAggregateWithNodeWasCreated | {} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site", "parentNodeAggregateId": "sites"} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a", "parentNodeAggregateId": "site"} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a1", "parentNodeAggregateId": "a", "originDimensionSpacePoint": {"language": "de"}, "succeedingSiblingsForCoverage": [{"dimensionSpacePoint":{"language": "de"},"nodeAggregateId":null},{"dimensionSpacePoint":{"language": "ch"},"nodeAggregateId":null}]} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "b", "parentNodeAggregateId": "site"} | + | NodeSpecializationVariantWasCreated | {"nodeAggregateId": "a1", "sourceOrigin": {"language": "de"}, "specializationOrigin": {"language": "ch"}, "specializationSiblings": [{"dimensionSpacePoint":{"language": "ch"},"nodeAggregateId":null}]} | + | NodeAggregateWasMoved | {"nodeAggregateId": "a1", "newParentNodeAggregateId": "b", "succeedingSiblingsForCoverage": [{"dimensionSpacePoint":{"language":"ch"},"nodeAggregateId":null}]} | Scenario: Node variant with different grand parent node (ancestor node was moved) - Note: There is only NodeAggregateWasMoved event for "a" and not for "a1" @@ -95,13 +95,13 @@ Feature: Migrating nodes with content dimensions | a1 | /sites/site/b/a/a1 | Some.Package:Thing | {"language": ["ch"]} | And I run the event migration Then I expect the following events to be exported - | Type | Payload | - | RootNodeAggregateWithNodeWasCreated | {} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site", "parentNodeAggregateId": "sites"} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a", "parentNodeAggregateId": "site"} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a1", "parentNodeAggregateId": "a", "originDimensionSpacePoint": {"language": "de"}, "succeedingSiblingsForCoverage": [{"dimensionSpacePoint":{"language": "de"},"nodeAggregateId":null}, {"dimensionSpacePoint":{"language": "ch"},"nodeAggregateId":null}]} | - | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "b", "parentNodeAggregateId": "site"} | - | NodeSpecializationVariantWasCreated | {"nodeAggregateId": "a", "sourceOrigin": {"language": "de"}, "specializationOrigin": {"language": "ch"}, "specializationSiblings": [{"dimensionSpacePoint":{"language": "ch"},"nodeAggregateId":null}]} | - | NodeAggregateWasMoved | {"nodeAggregateId": "a", "nodeMoveMappings": [{"movedNodeOrigin":{"language":"ch"},"newLocations":[{"coveredDimensionSpacePoint":{"language":"ch"},"newSucceedingSibling":{"nodeAggregateId":"b","originDimensionSpacePoint":{"language":"de"},"parentNodeAggregateId":"site","parentOriginDimensionSpacePoint":{"language":"de"}}}]}]} | - | NodeSpecializationVariantWasCreated | {"nodeAggregateId": "a1", "sourceOrigin": {"language": "de"}, "specializationOrigin": {"language": "ch"}, "specializationSiblings": [{"dimensionSpacePoint":{"language": "ch"},"nodeAggregateId":null}]} | + | Type | Payload | + | RootNodeAggregateWithNodeWasCreated | {} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "site", "parentNodeAggregateId": "sites"} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a", "parentNodeAggregateId": "site"} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "a1", "parentNodeAggregateId": "a", "originDimensionSpacePoint": {"language": "de"}, "succeedingSiblingsForCoverage": [{"dimensionSpacePoint":{"language": "de"},"nodeAggregateId":null}, {"dimensionSpacePoint":{"language": "ch"},"nodeAggregateId":null}]} | + | NodeAggregateWithNodeWasCreated | {"nodeAggregateId": "b", "parentNodeAggregateId": "site"} | + | NodeSpecializationVariantWasCreated | {"nodeAggregateId": "a", "sourceOrigin": {"language": "de"}, "specializationOrigin": {"language": "ch"}, "specializationSiblings": [{"dimensionSpacePoint":{"language": "ch"},"nodeAggregateId":null}]} | + | NodeAggregateWasMoved | {"nodeAggregateId": "a", "newParentNodeAggregateId": "b", "succeedingSiblingsForCoverage": [{"dimensionSpacePoint":{"language":"ch"},"nodeAggregateId":null}]} | + | NodeSpecializationVariantWasCreated | {"nodeAggregateId": "a1", "sourceOrigin": {"language": "de"}, "specializationOrigin": {"language": "ch"}, "specializationSiblings": [{"dimensionSpacePoint":{"language": "ch"},"nodeAggregateId":null}]} | From e94c1614914a232ad7fdf62ee783bc3e8f0a96bf Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Tue, 16 Apr 2024 18:30:05 +0200 Subject: [PATCH 033/214] make SubtreeTags private again --- .../Classes/Projection/ContentGraph/NodeTags.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php index d53233c328a..8317643ab22 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php @@ -33,8 +33,8 @@ final readonly class NodeTags implements \IteratorAggregate, \Countable, \JsonSerializable { private function __construct( - public SubtreeTags $tags, - public SubtreeTags $inheritedTags, + private SubtreeTags $tags, + private SubtreeTags $inheritedTags, ) { } From 3c88de486c28fdc6f26d79e2b4b3026e24684ecc Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Tue, 16 Apr 2024 18:31:02 +0200 Subject: [PATCH 034/214] Streamline Nodes::getNodeAggregateIds naming --- .../Classes/Feature/Common/ConstraintChecks.php | 6 +++--- .../Classes/Feature/NodeMove/NodeMove.php | 6 +++--- .../Classes/Projection/ContentGraph/Nodes.php | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index f8bf6372262..bfcb51b11c2 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -578,7 +578,7 @@ protected function requireNodeAggregateToBeSibling( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findSucceedingSiblingNodes($referenceNodeAggregateId, FindSucceedingSiblingNodesFilter::create()); - if ($succeedingSiblings->getIds()->contain($siblingNodeAggregateId)) { + if ($succeedingSiblings->getNodeAggregateIds()->contain($siblingNodeAggregateId)) { return; } @@ -587,7 +587,7 @@ protected function requireNodeAggregateToBeSibling( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findPrecedingSiblingNodes($referenceNodeAggregateId, FindPrecedingSiblingNodesFilter::create()); - if ($precedingSiblings->getIds()->contain($siblingNodeAggregateId)) { + if ($precedingSiblings->getNodeAggregateIds()->contain($siblingNodeAggregateId)) { return; } @@ -613,7 +613,7 @@ protected function requireNodeAggregateToBeChild( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findChildNodes($parentNodeAggregateId, FindChildNodesFilter::create()); - if ($childNodes->getIds()->contain($childNodeAggregateId)) { + if ($childNodes->getNodeAggregateIds()->contain($childNodeAggregateId)) { return; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index 3aa951e27d3..1f1e3266246 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -269,13 +269,13 @@ private function resolveInterdimensionalSiblingsForMove( ? $selectedSubgraph->findSucceedingSiblingNodes( $succeedingSiblingId, FindSucceedingSiblingNodesFilter::create() - )->getIds() + )->getNodeAggregateIds() : null; $alternativePrecedingSiblingIds = $precedingSiblingId ? $selectedSubgraph->findPrecedingSiblingNodes( $precedingSiblingId, FindPrecedingSiblingNodesFilter::create() - )->getIds() + )->getNodeAggregateIds() : null; $interdimensionalSiblings = []; @@ -354,7 +354,7 @@ private function resolveInterdimensionalSiblingsForMove( $variantSucceedingSiblingIds = $variantSubgraph->findSucceedingSiblingNodes( $variantPrecedingSiblingId, FindSucceedingSiblingNodesFilter::create(pagination: Pagination::fromLimitAndOffset(2, 0)) - )->getIds(); + )->getNodeAggregateIds(); $relevantVariantSucceedingSiblingId = null; foreach ($variantSucceedingSiblingIds as $variantSucceedingSiblingId) { if (!$variantSucceedingSiblingId->equals($nodeAggregateId)) { diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php index 461ab934305..43546e4b156 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php @@ -198,7 +198,7 @@ public function nextAll(Node $referenceNode): self return new self(array_slice($this->nodes, $referenceNodeIndex + 1)); } - public function getIds(): NodeAggregateIds + public function getNodeAggregateIds(): NodeAggregateIds { return NodeAggregateIds::create(...array_map( fn (Node $node): NodeAggregateId => $node->nodeAggregateId, From 333dcf676f35d0626754f4089c3107b80f8d9aa8 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Tue, 16 Apr 2024 18:31:54 +0200 Subject: [PATCH 035/214] Streamline exact tag check names --- .../05-MoveNodeAggregate_SubtreeTags.feature | 1280 ++++++++--------- .../Features/Bootstrap/ProjectedNodeTrait.php | 44 +- 2 files changed, 662 insertions(+), 662 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature index f38fdb6a08d..eb7d9ed22e4 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature @@ -63,46 +63,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move an untagged node to a new parent that tags itself partially Given the command TagSubtree is executed with payload: @@ -124,46 +124,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" # move untagged to tagged @@ -187,46 +187,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move an untagged node to a new parent that is partially tagged by its ancestors Given the command TagSubtree is executed with payload: @@ -248,46 +248,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" # move tagging to untagged @@ -312,46 +312,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself partially to a new, untagged parent @@ -374,46 +374,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" # move tagging to tagging @@ -446,46 +446,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself to a new parent that tags the same, partially @@ -516,46 +516,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself partially to a new parent that tags the same @@ -586,46 +586,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself partially to a new parent that tags the same, partially @@ -656,46 +656,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself to a new parent that tags differently @@ -726,46 +726,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1,tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1,tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself to a new parent that tags differently, partially @@ -796,46 +796,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1,tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself partially to a new parent that tags differently @@ -866,46 +866,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1,tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself partially to a new parent that tags differently, partially @@ -936,46 +936,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1,tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" # move tagging to tagged @@ -1008,46 +1008,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself to a new parent that is tagged the same, partially @@ -1078,46 +1078,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself partially to a new parent that is tagged the same @@ -1148,46 +1148,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself partially to a new parent that is tagged the same, partially @@ -1218,46 +1218,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself to a new parent that is tagged differently @@ -1288,46 +1288,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1,tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1,tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself to a new parent that is tagged differently, partially @@ -1358,46 +1358,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1,tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself partially to a new parent that tags differently @@ -1428,46 +1428,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1,tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a node that tags itself partially to a new parent that is tagged differently, partially @@ -1498,46 +1498,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1,tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1,tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" # move tagged to untagged @@ -1562,46 +1562,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a partially tagged node to a new, untagged parent @@ -1624,46 +1624,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" # move tagged to tagging @@ -1696,46 +1696,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a tagged node to a new parent that tags the same, partially @@ -1766,46 +1766,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "tag1" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a partially tagged node to a new parent that tags the same @@ -1836,46 +1836,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a partially tagged node to a new parent that tags the same, partially @@ -1906,46 +1906,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a tagged node to a new parent that tags differently @@ -1976,46 +1976,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a tagged node to a new parent that tags differently, partially @@ -2046,46 +2046,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a partially tagged node to a new parent that tags differently @@ -2116,46 +2116,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a partially tagged node to a new parent that tags differently, partially @@ -2186,46 +2186,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" # move tagged to tagged @@ -2258,46 +2258,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a tagged node to a new parent that is tagged the same, partially @@ -2328,46 +2328,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a partially tagged node to a new parent that is tagged the same @@ -2398,46 +2398,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a partially tagged node to a new parent that is tagged the same, partially @@ -2468,46 +2468,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag1" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag1" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a tagged node to a new parent that is tagged differently @@ -2538,46 +2538,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a tagged node to a new parent that is tagged differently, partially @@ -2608,46 +2608,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a partially tagged node to a new parent that is tagged differently @@ -2678,46 +2678,46 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" Scenario: Move a partially tagged node to a new parent that is tagged differently, partially @@ -2748,43 +2748,43 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "tag2" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "tag2" When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} - And I expect this node to tag with "" - And I expect this node to be tagged with "" + And I expect this node to be exactly explicitly tagged "" + And I expect this node to exactly inherit the tags "" diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index 85cea48b444..bee6d1ffec9 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -241,6 +241,28 @@ public function iExpectTheNodeWithAggregateIdentifierToNotContainTheTag(string $ }); } + /** + * @Then /^I expect this node to be exactly explicitly tagged "(.*)"$/ + * @param string $tagList the comma-separated list of tag names + */ + public function iExpectThisNodeToBeExactlyExplicitlyTagged(string $tagList): void + { + $this->assertOnCurrentNode(function (Node $currentNode) use ($tagList) { + $currentNode->tags->withoutInherited()->toStringArray() === explode(',', $tagList); + }); + } + + /** + * @Then /^I expect this node to exactly inherit the tags "(.*)"$/ + * @param string $tagList the comma-separated list of tag names + */ + public function iExpectThisNodeToExactlyInheritTheTags(string $tagList): void + { + $this->assertOnCurrentNode(function (Node $currentNode) use ($tagList) { + $currentNode->tags->onlyInherited()->toStringArray() === explode(',', $tagList); + }); + } + protected function initializeCurrentNodeFromContentGraph(callable $query): void { $this->currentNode = $query($this->currentContentRepository->getContentGraph()); @@ -650,28 +672,6 @@ public function iExpectThisNodeToHaveTheFollowingSucceedingSiblings(TableNode $e }); } - /** - * @Then /^I expect this node to tag with "(.*)"$/ - * @param string $tagList the comma-separated list of tag names - */ - public function iExpectThisNodeToTagWith(string $tagList): void - { - $this->assertOnCurrentNode(function (Node $currentNode) use ($tagList) { - $currentNode->tags->tags->toStringArray() === explode(',', $tagList); - }); - } - - /** - * @Then /^I expect this node to be tagged with "(.*)"$/ - * @param string $tagList the comma-separated list of tag names - */ - public function iExpectThisNodeToBeTaggedWith(string $tagList): void - { - $this->assertOnCurrentNode(function (Node $currentNode) use ($tagList) { - $currentNode->tags->inheritedTags->toStringArray() === explode(',', $tagList); - }); - } - /** * @Then /^I expect this node to have no succeeding siblings$/ */ From 1427c1fe649f691bd96e0465be6fd7f88f9a64fc Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Tue, 16 Apr 2024 19:22:45 +0200 Subject: [PATCH 036/214] Use event instead of command DSP for ChangeProjection --- .../PendingChangesProjection/ChangeProjection.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php b/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php index 6cbd8ffbb23..250cf0e5ab5 100644 --- a/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php +++ b/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php @@ -21,12 +21,12 @@ use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; +use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Event\DimensionSpacePointWasMoved; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; use Neos\ContentRepository\Core\Feature\NodeModification\Event\NodePropertiesWereSet; -use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; use Neos\ContentRepository\Core\Feature\NodeReferencing\Event\NodeReferencesWereSet; use Neos\ContentRepository\Core\Feature\NodeRemoval\Event\NodeAggregateWasRemoved; @@ -233,18 +233,19 @@ private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): vo private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event, EventEnvelope $eventEnvelope): void { - // Changes reflect the editorial intention, not the effect as they are later published via commands - $metadata = $eventEnvelope->event->metadata?->value ?? []; - if (isset($metadata['commandPayload'])) { - $command = MoveNodeAggregate::fromArray($metadata['commandPayload']); - // WORKAROUND: we simply use the command's DSP here as the origin dimension space point. + $affectedDimensionSpacePoints = iterator_to_array($event->succeedingSiblingsForCoverage->toDimensionSpacePointSet()); + $arbitraryDimensionSpacePoint = reset($affectedDimensionSpacePoints); + if ($arbitraryDimensionSpacePoint instanceof DimensionSpacePoint) { + // always the case due to constraint enforcement (at least one DSP is selected and must have a succeeding sibling or null) + + // WORKAROUND: we simply use the event's first DSP here as the origin dimension space point. // But this DSP is not necessarily occupied. // @todo properly handle this by storing the necessary information in the projection $this->markAsMoved( $event->getContentStreamId(), $event->getNodeAggregateId(), - OriginDimensionSpacePoint::fromDimensionSpacePoint($command->dimensionSpacePoint) + OriginDimensionSpacePoint::fromDimensionSpacePoint($arbitraryDimensionSpacePoint) ); } } From 53c3ed41248b0694bcd3266ab6f1ef3555d8de26 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:20:16 +0100 Subject: [PATCH 037/214] WIP: FEATURE: Fusion Runtime render directly http response --- Neos.Fusion/Classes/Core/Runtime.php | 39 +++++++++++++++++- Neos.Fusion/Classes/View/FusionView.php | 41 ++++++++++--------- .../View/Fixtures/Fusion/HttpResponse.fusion | 9 ++++ .../View/Fixtures/Fusion/Root.fusion | 4 ++ .../Tests/Functional/View/FusionViewTest.php | 36 ++++++++++++++++ 5 files changed, 108 insertions(+), 21 deletions(-) create mode 100644 Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/HttpResponse.fusion diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 67e121b068b..43a62a70484 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -11,6 +11,10 @@ * source code. */ +use GuzzleHttp\Psr7\Message; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\Utils; +use Psr\Http\Message\ResponseInterface; use Neos\Eel\Utility as EelUtility; use Neos\Flow\Annotations as Flow; use Neos\Flow\Configuration\Exception\InvalidConfigurationException; @@ -302,6 +306,39 @@ public function getLastEvaluationStatus() return $this->lastEvaluationStatus; } + public function renderResponse(string $fusionPath, array $contextArray): ResponseInterface + { + foreach ($contextArray as $key => $_) { + if ($this->fusionGlobals->has($key)) { + throw new Exception(sprintf('Overriding Fusion global variable "%s" via @context is not allowed.', $key), 1706452063); + } + } + $this->pushContextArray($contextArray); + try { + $output = $this->render($fusionPath); + } finally { + $this->popContext(); + } + + /** + * parse potential raw http response possibly rendered via "Neos.Fusion:Http.Message" + * {@see \Neos\Fusion\FusionObjects\HttpResponseImplementation} + */ + $outputStringHasHttpPreamble = is_string($output) && str_starts_with($output, 'HTTP/'); + if ($outputStringHasHttpPreamble) { + return Message::parseResponse($output); + } + + $stream = match(true) { + is_string($output), + $output instanceof \Stringable => Utils::streamFor((string)$output), + $output === null, $output === false => Utils::streamFor(''), + default => throw new \RuntimeException(sprintf('Cannot render %s into http response body.', get_debug_type($output)), 1706454898) + }; + + return new Response(body: $stream); + } + /** * Render an absolute Fusion path and return the result. * @@ -629,7 +666,7 @@ protected function prepareContextForFusionObject(AbstractFusionObject $fusionObj $newContextArray ??= $this->currentContext; foreach ($fusionConfiguration['__meta']['context'] as $contextKey => $contextValue) { if ($this->fusionGlobals->has($contextKey)) { - throw new Exception(sprintf('Overriding Fusion global variable "%s" via @context is not allowed.', $contextKey), 1694247627130); + throw new Exception(sprintf('Overriding Fusion global variable "%s" via @context is not allowed.', $contextKey), 1706452069); } $newContextArray[$contextKey] = $this->evaluate($fusionPath . '/__meta/context/' . $contextKey, $fusionObject, self::BEHAVIOR_EXCEPTION); } diff --git a/Neos.Fusion/Classes/View/FusionView.php b/Neos.Fusion/Classes/View/FusionView.php index bf983346f4f..37392406a57 100644 --- a/Neos.Fusion/Classes/View/FusionView.php +++ b/Neos.Fusion/Classes/View/FusionView.php @@ -22,6 +22,7 @@ use Neos\Fusion\Core\Runtime; use Neos\Fusion\Core\RuntimeFactory; use Neos\Fusion\Exception\RuntimeException; +use Psr\Http\Message\ResponseInterface; /** * View for using Fusion for standard MVC controllers. @@ -46,7 +47,8 @@ class FusionView extends AbstractView 'fusionGlobals' => [null, 'Additional global variables; merged together with the "request". Must only be specified at creation.', FusionGlobals::class], 'packageKey' => [null, 'The package key where the Fusion should be loaded from. If not given, is automatically derived from the current request.', 'string'], 'debugMode' => [false, 'Flag to enable debug mode of the Fusion runtime explicitly (overriding the global setting).', 'boolean'], - 'enableContentCache' => [false, 'Flag to enable content caching inside Fusion (overriding the global setting).', 'boolean'] + 'enableContentCache' => [false, 'Flag to enable content caching inside Fusion (overriding the global setting).', 'boolean'], + 'renderHttpResponse' => [false, 'Flag to render fusion as http repose for advanced form support and Neos.Fusion:Http.ResponseHead support.', 'boolean'], ]; /** @@ -139,13 +141,29 @@ public function setFusionPathPatterns(array $pathPatterns) /** * Render the view * - * @return mixed The rendered view + * @return mixed|ResponseInterface The rendered view * @api */ public function render() { $this->initializeFusionRuntime(); - return $this->renderFusion(); + + if ($this->getOption('renderHttpResponse') === true) { + try { + return $this->fusionRuntime->renderResponse($this->getFusionPathForCurrentRequest(), $this->variables); + } catch (RuntimeException $exception) { + throw $exception->getPrevious(); + } + } else { + try { + $this->fusionRuntime->pushContextArray($this->variables); + return $this->fusionRuntime->render($this->getFusionPathForCurrentRequest()); + } catch (RuntimeException $exception) { + throw $exception->getPrevious(); + } finally { + $this->fusionRuntime->popContext(); + } + } } /** @@ -283,21 +301,4 @@ protected function getFusionPathForCurrentRequest() } return $this->fusionPath; } - - /** - * Render the given Fusion and return the rendered page - * @return mixed - * @throws \Exception - */ - protected function renderFusion() - { - $this->fusionRuntime->pushContextArray($this->variables); - try { - $output = $this->fusionRuntime->render($this->getFusionPathForCurrentRequest()); - } catch (RuntimeException $exception) { - throw $exception->getPrevious(); - } - $this->fusionRuntime->popContext(); - return $output; - } } diff --git a/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/HttpResponse.fusion b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/HttpResponse.fusion new file mode 100644 index 00000000000..d75b5c47d1f --- /dev/null +++ b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/HttpResponse.fusion @@ -0,0 +1,9 @@ + +response = Neos.Fusion:Http.Message { + httpResponseHead { + statusCode = 404 + headers.Content-Type = 'application/json' + } + + body = '{"some":"json"}' +} diff --git a/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/Root.fusion b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/Root.fusion index b190f6bd5a0..fbcf0fc40a3 100644 --- a/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/Root.fusion +++ b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/Root.fusion @@ -1 +1,5 @@ include: ./**/*.fusion +include: 'resource://Neos.Fusion/Private/Fusion/Prototypes/Join.fusion' +include: 'resource://Neos.Fusion/Private/Fusion/Prototypes/DataStructure.fusion' +include: 'resource://Neos.Fusion/Private/Fusion/Prototypes/Http.Message.fusion' +include: 'resource://Neos.Fusion/Private/Fusion/Prototypes/Http.ResponseHead.fusion' diff --git a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php index dc2f06d8ba9..629508c8643 100644 --- a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php +++ b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php @@ -15,6 +15,7 @@ use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Flow\Tests\FunctionalTestCase; use Neos\Fusion\View\FusionView; +use Psr\Http\Message\ResponseInterface; /** * Testcase for the Fusion View @@ -64,6 +65,41 @@ public function fusionViewOutputsVariable() self::assertEquals('XHallo Welt', $view->render()); } + /** + * @test + */ + public function fusionViewCanReturnHttpResponse() + { + $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); + $view->setOption('renderHttpResponse', true); + $view->assign('test', 'Hallo Welt'); + $response = $view->render(); + self::assertInstanceOf(ResponseInterface::class, $response); + self::assertEquals('XHallo Welt', $view->render()->getBody()->getContents()); + } + + /** + * @test + */ + public function fusionViewCanReturnHttpResponseFromHttpMessagePrototype() + { + $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); + $view->setFusionPath('response'); + self::assertSame(<<render()); + + $view->setOption('renderHttpResponse', true); + $response = $view->render(); + self::assertInstanceOf(ResponseInterface::class, $response); + self::assertSame('{"some":"json"}', $response->getBody()->getContents()); + self::assertSame(404, $response->getStatusCode()); + self::assertSame("application/json", $response->getHeaderLine("Content-Type")); + } + /** * Prepare a FusionView for testing that Mocks a request with the given controller and action names. * From bd64cdd7520b14fa9f4aff0ede03e97b0057d169 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 28 Jan 2024 16:24:23 +0100 Subject: [PATCH 038/214] WIP: Neos Fusion View use Runtime::renderResponse --- Neos.Neos/Classes/View/FusionView.php | 48 ++++----------------------- 1 file changed, 7 insertions(+), 41 deletions(-) diff --git a/Neos.Neos/Classes/View/FusionView.php b/Neos.Neos/Classes/View/FusionView.php index 2f95ba9010f..0e5d019bb8f 100644 --- a/Neos.Neos/Classes/View/FusionView.php +++ b/Neos.Neos/Classes/View/FusionView.php @@ -57,11 +57,11 @@ class FusionView extends AbstractView /** * Renders the view * - * @return string|ResponseInterface The rendered view + * @return ResponseInterface The rendered view * @throws \Exception if no node is given * @api */ - public function render(): string|ResponseInterface + public function render(): ResponseInterface { $currentNode = $this->getCurrentNode(); @@ -76,20 +76,15 @@ public function render(): string|ResponseInterface $this->setFallbackRuleFromDimension($currentNode->subgraphIdentity->dimensionSpacePoint); - $fusionRuntime->pushContextArray([ - 'node' => $currentNode, - 'documentNode' => $this->getClosestDocumentNode($currentNode) ?: $currentNode, - 'site' => $currentSiteNode - ]); try { - $output = $fusionRuntime->render($this->fusionPath); - $output = $this->parsePotentialRawHttpResponse($output); + return $fusionRuntime->renderResponse($this->fusionPath, [ + 'node' => $currentNode, + 'documentNode' => $this->getClosestDocumentNode($currentNode) ?: $currentNode, + 'site' => $currentSiteNode + ]); } catch (RuntimeException $exception) { throw $exception->getPrevious() ?: $exception; } - $fusionRuntime->popContext(); - - return $output; } /** @@ -131,35 +126,6 @@ public function render(): string|ResponseInterface */ protected $securityContext; - /** - * @param string $output - * @return string|ResponseInterface If output is a string with a HTTP preamble a ResponseInterface - * otherwise the original output. - */ - protected function parsePotentialRawHttpResponse($output) - { - if ($this->isRawHttpResponse($output)) { - return Message::parseResponse($output); - } - - return $output; - } - - /** - * Checks if the mixed input looks like a raw HTTTP response. - * - * @param mixed $value - * @return bool - */ - protected function isRawHttpResponse($value): bool - { - if (is_string($value) && strpos($value, 'HTTP/') === 0) { - return true; - } - - return false; - } - /** * Is it possible to render $node with $his->fusionPath? * From 95169a6c7e711a3504610fe1362a5de9d7114a9e Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:00:52 +0100 Subject: [PATCH 039/214] WIP: Hacky HttpResponseConstraints --- .../Classes/Core/HttpResponseConstraints.php | 100 ++++++++++++++++++ Neos.Fusion/Classes/Core/Runtime.php | 11 +- 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 Neos.Fusion/Classes/Core/HttpResponseConstraints.php diff --git a/Neos.Fusion/Classes/Core/HttpResponseConstraints.php b/Neos.Fusion/Classes/Core/HttpResponseConstraints.php new file mode 100644 index 00000000000..89b58b3bdd3 --- /dev/null +++ b/Neos.Fusion/Classes/Core/HttpResponseConstraints.php @@ -0,0 +1,100 @@ +partialResponse = new Response(); + } + + /** + * Gets the response status code. + * + * @return int Status code. + */ + public function getStatusCode() + { + return $this->partialResponse->getStatusCode(); + } + + /** + * @param int $code The 3-digit integer result code to set. + */ + public function setStatus(int $code) + { + $this->partialResponse = $this->partialResponse->withStatus($code); + } + + /** + * Retrieves all message header values. + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getPartialResponse(): ResponseInterface + { + return $this->partialResponse->getHeaders(); + } + + /** + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + */ + public function setHeader(string $name, $value) + { + $this->partialResponse = $this->partialResponse->withHeader($name, $value); + } + + /** + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + */ + public function setAndMergeHeader(string $name, $value) + { + $this->partialResponse = $this->partialResponse->withAddedHeader($name, $value); + } + + /** + * @param string $name Case-insensitive header field name to remove. + */ + public function unsetHeader(string $name) + { + $this->partialResponse = $this->partialResponse->withoutHeader($name); + } + + public function applyToResponse(ResponseInterface $response): ResponseInterface + { + foreach ($this->partialResponse->getHeaders() as $name => $values) { + $response = $response->withAddedHeader($name, $values); + } + + // preserve non 200 status codes that would otherwise be overwritten + if ($this->partialResponse->getStatusCode() !== 200) { + $response = $response->withStatus($this->partialResponse->getStatusCode()); + } + + $this->partialResponse = new Response(); + + return $response; + } +} diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 43a62a70484..dac7cb6a822 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -114,6 +114,8 @@ class Runtime */ public readonly FusionGlobals $fusionGlobals; + public readonly HttpResponseConstraints $unsafeHttpResponseConstrains; + /** * @var RuntimeConfiguration */ @@ -159,6 +161,7 @@ public function __construct( ); $this->runtimeContentCache = new RuntimeContentCache($this); $this->fusionGlobals = $fusionGlobals; + $this->unsafeHttpResponseConstrains = new HttpResponseConstraints(); } /** @@ -326,7 +329,9 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons */ $outputStringHasHttpPreamble = is_string($output) && str_starts_with($output, 'HTTP/'); if ($outputStringHasHttpPreamble) { - return Message::parseResponse($output); + return $this->unsafeHttpResponseConstrains->applyToResponse( + Message::parseResponse($output) + ); } $stream = match(true) { @@ -336,7 +341,9 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons default => throw new \RuntimeException(sprintf('Cannot render %s into http response body.', get_debug_type($output)), 1706454898) }; - return new Response(body: $stream); + return $this->unsafeHttpResponseConstrains->applyToResponse( + new Response(body: $stream) + ); } /** From d567bdc2f2dbf9643cc67406b92c9fbac9bd2d14 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:21:09 +0100 Subject: [PATCH 040/214] WIP: Hurray make Runtime independent of ControllerContext + working legacy layer for manipulating getResponse and accessing getRequest --- .../Classes/Core/HttpResponseConstraints.php | 14 +++++ Neos.Fusion/Classes/Core/Runtime.php | 55 +++++++++---------- Neos.Fusion/Classes/Core/RuntimeFactory.php | 26 ++------- Neos.Fusion/Classes/View/FusionView.php | 3 - .../Classes/View/FusionExceptionView.php | 1 - Neos.Neos/Classes/View/FusionView.php | 1 - 6 files changed, 45 insertions(+), 55 deletions(-) diff --git a/Neos.Fusion/Classes/Core/HttpResponseConstraints.php b/Neos.Fusion/Classes/Core/HttpResponseConstraints.php index 89b58b3bdd3..7b774c36e73 100644 --- a/Neos.Fusion/Classes/Core/HttpResponseConstraints.php +++ b/Neos.Fusion/Classes/Core/HttpResponseConstraints.php @@ -5,6 +5,7 @@ namespace Neos\Fusion\Core; use GuzzleHttp\Psr7\Response; +use Neos\Flow\Mvc\ActionResponse; use Psr\Http\Message\ResponseInterface; final class HttpResponseConstraints @@ -16,6 +17,19 @@ public function __construct() $this->partialResponse = new Response(); } + /** + * @deprecated + */ + public static function createFromActionResponse(?ActionResponse $actionResponse) + { + $constraints = new self(); + if (!$actionResponse) { + return $constraints; + } + $constraints->partialResponse = $actionResponse->buildHttpResponse(); + return $constraints; + } + /** * Gets the response status code. * diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index dac7cb6a822..33fad6bc8f5 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -124,7 +124,7 @@ class Runtime /** * @deprecated */ - protected ControllerContext $controllerContext; + protected ?ControllerContext $controllerContext = null; /** * @var array @@ -164,15 +164,6 @@ public function __construct( $this->unsafeHttpResponseConstrains = new HttpResponseConstraints(); } - /** - * @deprecated {@see self::getControllerContext()} - * @internal - */ - public function setControllerContext(ControllerContext $controllerContext): void - { - $this->controllerContext = $controllerContext; - } - /** * Returns the context which has been passed by the currently active MVC Controller * @@ -183,23 +174,10 @@ public function setControllerContext(ControllerContext $controllerContext): void */ public function getControllerContext(): ControllerContext { - if (isset($this->controllerContext)) { - return $this->controllerContext; - } - - if (!($request = $this->fusionGlobals->get('request')) instanceof ActionRequest) { - throw new Exception(sprintf('Expected Fusion variable "request" to be of type ActionRequest, got value of type "%s".', get_debug_type($request)), 1693558026485); + if ($this->controllerContext === null) { + throw new Exception(sprintf('Legacy controller context in runtime is only available when fusion global "request" is a ActionRequest and during "renderResponse".'), 1706458355); } - - $uriBuilder = new UriBuilder(); - $uriBuilder->setRequest($request); - - return $this->controllerContext = new ControllerContext( - $request, - new ActionResponse(), - new Arguments([]), - $uriBuilder - ); + return $this->controllerContext; } /** @@ -311,6 +289,20 @@ public function getLastEvaluationStatus() public function renderResponse(string $fusionPath, array $contextArray): ResponseInterface { + // legacy controller context layer + $possibleRequest = $this->fusionGlobals->get('request'); + if ($possibleRequest instanceof ActionRequest) { + $uriBuilder = new UriBuilder(); + $uriBuilder->setRequest($possibleRequest); + + $this->controllerContext = new ControllerContext( + $possibleRequest, + new ActionResponse(), + new Arguments([]), + $uriBuilder + ); + } + foreach ($contextArray as $key => $_) { if ($this->fusionGlobals->has($key)) { throw new Exception(sprintf('Overriding Fusion global variable "%s" via @context is not allowed.', $key), 1706452063); @@ -323,6 +315,9 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons $this->popContext(); } + $legacyControllerContextResponseConstraints = HttpResponseConstraints::createFromActionResponse($this->controllerContext?->getResponse()); + $this->controllerContext = null; + /** * parse potential raw http response possibly rendered via "Neos.Fusion:Http.Message" * {@see \Neos\Fusion\FusionObjects\HttpResponseImplementation} @@ -330,7 +325,9 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons $outputStringHasHttpPreamble = is_string($output) && str_starts_with($output, 'HTTP/'); if ($outputStringHasHttpPreamble) { return $this->unsafeHttpResponseConstrains->applyToResponse( - Message::parseResponse($output) + $legacyControllerContextResponseConstraints->applyToResponse( + Message::parseResponse($output) + ) ); } @@ -342,7 +339,9 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons }; return $this->unsafeHttpResponseConstrains->applyToResponse( - new Response(body: $stream) + $legacyControllerContextResponseConstraints->applyToResponse( + new Response(body: $stream) + ) ); } diff --git a/Neos.Fusion/Classes/Core/RuntimeFactory.php b/Neos.Fusion/Classes/Core/RuntimeFactory.php index 8cd07a696d8..738d631f528 100644 --- a/Neos.Fusion/Classes/Core/RuntimeFactory.php +++ b/Neos.Fusion/Classes/Core/RuntimeFactory.php @@ -45,19 +45,18 @@ class RuntimeFactory */ public function create(array $fusionConfiguration, ControllerContext $controllerContext = null): Runtime { - if ($controllerContext === null) { - $controllerContext = self::createControllerContextFromEnvironment(); - } $defaultContextVariables = EelUtility::getDefaultContextVariables( $this->defaultContextConfiguration ?? [] ); $runtime = new Runtime( FusionConfiguration::fromArray($fusionConfiguration), FusionGlobals::fromArray( - ['request' => $controllerContext->getRequest(), ...$defaultContextVariables] + [ + 'request' => $controllerContext?->getRequest() ?? ActionRequest::fromHttpRequest(ServerRequest::fromGlobals()), + ...$defaultContextVariables + ] ) ); - $runtime->setControllerContext($controllerContext); return $runtime; } @@ -82,21 +81,4 @@ public function createFromSourceCode( $fusionGlobals ); } - - private static function createControllerContextFromEnvironment(): ControllerContext - { - $httpRequest = ServerRequest::fromGlobals(); - - $request = ActionRequest::fromHttpRequest($httpRequest); - - $uriBuilder = new UriBuilder(); - $uriBuilder->setRequest($request); - - return new ControllerContext( - $request, - new ActionResponse(), - new Arguments([]), - $uriBuilder - ); - } } diff --git a/Neos.Fusion/Classes/View/FusionView.php b/Neos.Fusion/Classes/View/FusionView.php index 37392406a57..bdc4413e113 100644 --- a/Neos.Fusion/Classes/View/FusionView.php +++ b/Neos.Fusion/Classes/View/FusionView.php @@ -194,9 +194,6 @@ public function initializeFusionRuntime() $this->parsedFusion, $fusionGlobals ); - if (isset($this->controllerContext)) { - $this->fusionRuntime->setControllerContext($this->controllerContext); - } } if (isset($this->options['debugMode'])) { $this->fusionRuntime->setDebugMode($this->options['debugMode']); diff --git a/Neos.Neos/Classes/View/FusionExceptionView.php b/Neos.Neos/Classes/View/FusionExceptionView.php index bf4807b0e65..46241f48729 100644 --- a/Neos.Neos/Classes/View/FusionExceptionView.php +++ b/Neos.Neos/Classes/View/FusionExceptionView.php @@ -216,7 +216,6 @@ protected function getFusionRuntime( $fusionConfiguration, $fusionGlobals ); - $this->fusionRuntime->setControllerContext($controllerContext); if (isset($this->options['enableContentCache']) && $this->options['enableContentCache'] !== null) { $this->fusionRuntime->setEnableContentCache($this->options['enableContentCache']); diff --git a/Neos.Neos/Classes/View/FusionView.php b/Neos.Neos/Classes/View/FusionView.php index 0e5d019bb8f..82e29a29634 100644 --- a/Neos.Neos/Classes/View/FusionView.php +++ b/Neos.Neos/Classes/View/FusionView.php @@ -212,7 +212,6 @@ protected function getFusionRuntime(Node $currentSiteNode) $fusionConfiguration, $fusionGlobals ); - $this->fusionRuntime->setControllerContext($this->controllerContext); if (isset($this->options['enableContentCache']) && $this->options['enableContentCache'] !== null) { $this->fusionRuntime->setEnableContentCache($this->options['enableContentCache']); From 1aeaa49a97f7485f4d474d3a20b46bffc5ffcedc Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:28:15 +0100 Subject: [PATCH 041/214] WIP: Migrate PluginImplementation to use `unsafeHttpResponseConstrains` --- Neos.Fusion/Classes/Core/HttpResponseConstraints.php | 9 ++------- Neos.Fusion/Classes/Core/Runtime.php | 5 ++++- Neos.Neos/Classes/Fusion/PluginImplementation.php | 7 +------ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/Neos.Fusion/Classes/Core/HttpResponseConstraints.php b/Neos.Fusion/Classes/Core/HttpResponseConstraints.php index 7b774c36e73..2c7a98df064 100644 --- a/Neos.Fusion/Classes/Core/HttpResponseConstraints.php +++ b/Neos.Fusion/Classes/Core/HttpResponseConstraints.php @@ -20,14 +20,9 @@ public function __construct() /** * @deprecated */ - public static function createFromActionResponse(?ActionResponse $actionResponse) + public function setAndMergeFromActionResponse(ActionResponse $actionResponse) { - $constraints = new self(); - if (!$actionResponse) { - return $constraints; - } - $constraints->partialResponse = $actionResponse->buildHttpResponse(); - return $constraints; + $this->partialResponse = $this->applyToResponse($actionResponse->buildHttpResponse()); } /** diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 33fad6bc8f5..6e2e26ffe2a 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -315,7 +315,10 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons $this->popContext(); } - $legacyControllerContextResponseConstraints = HttpResponseConstraints::createFromActionResponse($this->controllerContext?->getResponse()); + $legacyControllerContextResponseConstraints = new HttpResponseConstraints(); + if ($this->controllerContext) { + $legacyControllerContextResponseConstraints->setAndMergeFromActionResponse($this->controllerContext->getResponse()); + } $this->controllerContext = null; /** diff --git a/Neos.Neos/Classes/Fusion/PluginImplementation.php b/Neos.Neos/Classes/Fusion/PluginImplementation.php index c623542afd9..02c388453f5 100644 --- a/Neos.Neos/Classes/Fusion/PluginImplementation.php +++ b/Neos.Neos/Classes/Fusion/PluginImplementation.php @@ -166,17 +166,12 @@ public function evaluate(): string $currentContext = $this->runtime->getCurrentContext(); $this->node = $currentContext['node']; $this->documentNode = $currentContext['documentNode']; - $parentResponse = $this->runtime->getControllerContext()->getResponse(); $pluginResponse = new ActionResponse(); $this->dispatcher->dispatch($this->buildPluginRequest(), $pluginResponse); - // We need to make sure to not merge content up into the parent ActionResponse - // because that would break the Fusion HttpResponse. $content = $pluginResponse->getContent(); - $pluginResponse->setContent(''); - - $pluginResponse->mergeIntoParentResponse($parentResponse); + $this->runtime->unsafeHttpResponseConstrains->setAndMergeFromActionResponse($pluginResponse); return $content; } From b9128fb239cbbfcfd8677b465be62bcf29ddeeda Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:44:41 +0100 Subject: [PATCH 042/214] WIP: FusionObject to not depend on the controller context --- .../FusionObjects/TemplateImplementation.php | 2 +- .../FusionObjects/UriBuilderImplementation.php | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Neos.Fusion/Classes/FusionObjects/TemplateImplementation.php b/Neos.Fusion/Classes/FusionObjects/TemplateImplementation.php index 2287410a718..4ccc72f2381 100644 --- a/Neos.Fusion/Classes/FusionObjects/TemplateImplementation.php +++ b/Neos.Fusion/Classes/FusionObjects/TemplateImplementation.php @@ -79,7 +79,7 @@ public function getPath() */ public function evaluate() { - $actionRequest = $this->runtime->getControllerContext()->getRequest(); + $actionRequest = $this->runtime->fusionGlobals->get('request'); if (!$actionRequest instanceof ActionRequest) { $actionRequest = null; } diff --git a/Neos.Fusion/Classes/FusionObjects/UriBuilderImplementation.php b/Neos.Fusion/Classes/FusionObjects/UriBuilderImplementation.php index ce3ef08ae15..032992e051a 100644 --- a/Neos.Fusion/Classes/FusionObjects/UriBuilderImplementation.php +++ b/Neos.Fusion/Classes/FusionObjects/UriBuilderImplementation.php @@ -11,6 +11,10 @@ * source code. */ +use GuzzleHttp\Psr7\ServerRequest; +use Neos\Flow\Mvc\ActionRequest; +use Neos\Flow\Mvc\Routing\UriBuilder; + /** * A Fusion UriBuilder object @@ -150,8 +154,16 @@ public function isAbsolute() */ public function evaluate() { - $controllerContext = $this->runtime->getControllerContext(); - $uriBuilder = $controllerContext->getUriBuilder()->reset(); + $uriBuilder = new UriBuilder(); + $possibleRequest = $this->runtime->fusionGlobals->get('request'); + if ($possibleRequest instanceof ActionRequest) { + $uriBuilder->setRequest($possibleRequest); + } else { + // legacy + $uriBuilder->setRequest( + ActionRequest::fromHttpRequest(ServerRequest::fromGlobals()) + ); + } $format = $this->getFormat(); if ($format !== null) { From c3d36813068ae6a007a90350e3501b40dbbe5cc7 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:49:53 +0100 Subject: [PATCH 043/214] WIP: Skip non working test :( --- .../Classes/Core/HttpResponseConstraints.php | 1 + Neos.Fusion/Classes/Core/Runtime.php | 2 +- Neos.Fusion/Classes/Core/RuntimeFactory.php | 3 --- .../UriBuilderImplementation.php | 1 - .../Unit/Fusion/PluginImplementationTest.php | 21 +++++++++++++++---- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Neos.Fusion/Classes/Core/HttpResponseConstraints.php b/Neos.Fusion/Classes/Core/HttpResponseConstraints.php index 2c7a98df064..2f572cf6471 100644 --- a/Neos.Fusion/Classes/Core/HttpResponseConstraints.php +++ b/Neos.Fusion/Classes/Core/HttpResponseConstraints.php @@ -102,6 +102,7 @@ public function applyToResponse(ResponseInterface $response): ResponseInterface $response = $response->withStatus($this->partialResponse->getStatusCode()); } + // reset internal state $this->partialResponse = new Response(); return $response; diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 6e2e26ffe2a..b84f4b45fd2 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -334,7 +334,7 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons ); } - $stream = match(true) { + $stream = match (true) { is_string($output), $output instanceof \Stringable => Utils::streamFor((string)$output), $output === null, $output === false => Utils::streamFor(''), diff --git a/Neos.Fusion/Classes/Core/RuntimeFactory.php b/Neos.Fusion/Classes/Core/RuntimeFactory.php index 738d631f528..252c45170df 100644 --- a/Neos.Fusion/Classes/Core/RuntimeFactory.php +++ b/Neos.Fusion/Classes/Core/RuntimeFactory.php @@ -15,10 +15,7 @@ use Neos\Eel\Utility as EelUtility; use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\ActionRequest; -use Neos\Flow\Mvc\ActionResponse; -use Neos\Flow\Mvc\Controller\Arguments; use Neos\Flow\Mvc\Controller\ControllerContext; -use Neos\Flow\Mvc\Routing\UriBuilder; /** * @Flow\Scope("singleton") diff --git a/Neos.Fusion/Classes/FusionObjects/UriBuilderImplementation.php b/Neos.Fusion/Classes/FusionObjects/UriBuilderImplementation.php index 032992e051a..2e0eae65ff3 100644 --- a/Neos.Fusion/Classes/FusionObjects/UriBuilderImplementation.php +++ b/Neos.Fusion/Classes/FusionObjects/UriBuilderImplementation.php @@ -15,7 +15,6 @@ use Neos\Flow\Mvc\ActionRequest; use Neos\Flow\Mvc\Routing\UriBuilder; - /** * A Fusion UriBuilder object * diff --git a/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php b/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php index e8aff26d8ea..90c721757bb 100644 --- a/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php +++ b/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php @@ -11,6 +11,7 @@ * source code. */ +use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\Uri; use Neos\Flow\Mvc\ActionRequest; use Neos\Flow\Mvc\ActionResponse; @@ -18,6 +19,8 @@ use Neos\Flow\Mvc\Dispatcher; use Neos\Flow\Mvc\RequestInterface; use Neos\Flow\Tests\UnitTestCase; +use Neos\Fusion\Core\FusionConfiguration; +use Neos\Fusion\Core\FusionGlobals; use Neos\Fusion\Core\Runtime; use Neos\Neos\Fusion\PluginImplementation; use PHPUnit\Framework\MockObject\MockObject; @@ -79,7 +82,7 @@ public function setUp(): void $this->mockControllerContext = $this->getMockBuilder(ControllerContext::class)->disableOriginalConstructor()->getMock(); $this->mockControllerContext->method('getRequest')->willReturn($this->mockActionRequest); - $this->mockRuntime = $this->getMockBuilder(Runtime::class)->disableOriginalConstructor()->getMock(); + $this->mockRuntime = $this->getMockBuilder(Runtime::class)->setConstructorArgs([FusionConfiguration::fromArray([]), FusionGlobals::fromArray([])])->getMock(); $this->mockRuntime->method('getControllerContext')->willReturn($this->mockControllerContext); $this->pluginImplementation->_set('runtime', $this->mockRuntime); @@ -96,12 +99,12 @@ public function responseHeadersDataProvider(): array [ 'Plugin response key does already exist in parent with same value', ['parent' => ['key' => 'value'], 'plugin' => ['key' => 'value']], - ['key' => 'value'] + ['key' => 'value'] // 'value, value' ], [ 'Plugin response key does not exist in parent with different value', ['parent' => ['key' => 'value'], 'plugin' => ['key' => 'otherValue']], - ['key' => 'otherValue'] + ['key' => 'otherValue'] // 'otherValue, value' ], [ 'Plugin response key does not exist in parent', @@ -119,6 +122,8 @@ public function responseHeadersDataProvider(): array */ public function evaluateSetHeaderIntoParent(string $message, array $input, array $expected): void { + $this->markTestSkipped('DOESNT WORK.'); + $this->pluginImplementation->method('buildPluginRequest')->willReturn($this->mockActionRequest); $parentResponse = new ActionResponse(); @@ -133,8 +138,16 @@ public function evaluateSetHeaderIntoParent(string $message, array $input, array $this->pluginImplementation->evaluate(); + // in the runtime would be: + $runtimeResponse = $this->mockRuntime->unsafeHttpResponseConstrains->applyToResponse(new Response()); + + // in the action would be: + $parentResponse->replaceHttpResponse($runtimeResponse); + foreach ($expected as $expectedKey => $expectedValue) { - self::assertEquals($expectedValue, (string)$parentResponse->getHttpHeader($expectedKey), $message); + // previously tests succeeded: + // self::assertEquals($expectedValue, join(', ', \Neos\Utility\ObjectAccess::getProperty($parentResponse, 'headers', true)[$expectedKey]), $message); + self::assertEquals($expectedValue, $parentResponse->buildHttpResponse()->getHeaderLine($expectedKey), $message); } } From 6ae0b24687336371e32b2646dec49945c7b7af1b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:24:11 +0100 Subject: [PATCH 044/214] TASK: Migrate further fusion objects to `$this->runtime->fusionGlobals->get('request');` --- .../UriBuilderImplementation.php | 5 +++- .../Fusion/ConvertUrisImplementation.php | 26 +++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Neos.Fusion/Classes/FusionObjects/UriBuilderImplementation.php b/Neos.Fusion/Classes/FusionObjects/UriBuilderImplementation.php index 2e0eae65ff3..f59cdde7359 100644 --- a/Neos.Fusion/Classes/FusionObjects/UriBuilderImplementation.php +++ b/Neos.Fusion/Classes/FusionObjects/UriBuilderImplementation.php @@ -158,7 +158,10 @@ public function evaluate() if ($possibleRequest instanceof ActionRequest) { $uriBuilder->setRequest($possibleRequest); } else { - // legacy + // unfortunately, the uri-builder always needs a request at hand and cannot build uris without + // even, if the default param merging would not be required + // this will improve with a reformed uri building: + // https://github.com/neos/flow-development-collection/pull/2744 $uriBuilder->setRequest( ActionRequest::fromHttpRequest(ServerRequest::fromGlobals()) ); diff --git a/Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php b/Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php index 0254272e55d..99cc98edb44 100644 --- a/Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php +++ b/Neos.Neos/Classes/Fusion/ConvertUrisImplementation.php @@ -14,11 +14,13 @@ namespace Neos\Neos\Fusion; +use GuzzleHttp\Psr7\ServerRequest; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\Log\Utility\LogEnvironment; +use Neos\Flow\Mvc\ActionRequest; use Neos\Flow\Mvc\Exception\NoMatchingRouteException; use Neos\Flow\Mvc\Routing\UriBuilder; use Neos\Flow\ResourceManagement\ResourceManager; @@ -103,7 +105,6 @@ class ConvertUrisImplementation extends AbstractFusionObject */ public function evaluate() { - $text = $this->fusionValue('value'); if ($text === '' || $text === null) { @@ -149,12 +150,23 @@ public function evaluate() NodeAggregateId::fromString($matches[2]) ); $uriBuilder = new UriBuilder(); - $uriBuilder->setRequest($this->runtime->getControllerContext()->getRequest()); + $possibleRequest = $this->runtime->fusionGlobals->get('request'); + if ($possibleRequest instanceof ActionRequest) { + $uriBuilder->setRequest($possibleRequest); + } else { + // unfortunately, the uri-builder always needs a request at hand and cannot build uris without + // even, if the default param merging would not be required + // this will improve with a reformed uri building: + // https://github.com/neos/flow-development-collection/pull/2744 + $uriBuilder->setRequest( + ActionRequest::fromHttpRequest(ServerRequest::fromGlobals()) + ); + } $uriBuilder->setCreateAbsoluteUri($absolute); try { $resolvedUri = (string)NodeUriBuilder::fromUriBuilder($uriBuilder)->uriFor($nodeAddress); } catch (NoMatchingRouteException) { - $this->systemLogger->warning(sprintf('Could not resolve "%s" to a node uri. Arguments: %s', $matches[0], json_encode($uriBuilder->getLastArguments())), LogEnvironment::fromMethodName(__METHOD__)); + $this->systemLogger->info(sprintf('Could not resolve "%s" to a live node uri. Arguments: %s', $matches[0], json_encode($uriBuilder->getLastArguments())), LogEnvironment::fromMethodName(__METHOD__)); } $this->runtime->addCacheTag( CacheTag::forDynamicNodeAggregate($contentRepository->id, $nodeAddress->contentStreamId, NodeAggregateId::fromString($matches[2]))->value @@ -205,8 +217,12 @@ protected function replaceLinkTargets($processedContent) $setExternal = $this->fusionValue('setExternal'); $externalLinkTarget = \trim((string)$this->fusionValue('externalLinkTarget')); $resourceLinkTarget = \trim((string)$this->fusionValue('resourceLinkTarget')); - $controllerContext = $this->runtime->getControllerContext(); - $host = $controllerContext->getRequest()->getHttpRequest()->getUri()->getHost(); + $possibleRequest = $this->runtime->fusionGlobals->get('request'); + if ($possibleRequest instanceof ActionRequest) { + $host = $possibleRequest->getHttpRequest()->getUri()->getHost(); + } else { + $host = null; + } $processedContent = \preg_replace_callback( '~~i', static function ($matches) use ($externalLinkTarget, $resourceLinkTarget, $host, $setNoOpener, $setExternal) { From 6298673e9d492ec56f7fe77196ff8c8f964cf25b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:42:48 +0100 Subject: [PATCH 045/214] Revert introduction of `HttpResponseConstraints` and api in runtime to allow setting headers dynamically Instead, the legacy layer > $this->runtime->getControllerContext()->getResponse(); should be continued to be used by fusion forms and fusion plugin impl. --- .../Classes/Core/HttpResponseConstraints.php | 110 ------------------ Neos.Fusion/Classes/Core/Runtime.php | 49 ++++---- .../Classes/Fusion/PluginImplementation.php | 7 +- .../Unit/Fusion/PluginImplementationTest.php | 21 +--- 4 files changed, 39 insertions(+), 148 deletions(-) delete mode 100644 Neos.Fusion/Classes/Core/HttpResponseConstraints.php diff --git a/Neos.Fusion/Classes/Core/HttpResponseConstraints.php b/Neos.Fusion/Classes/Core/HttpResponseConstraints.php deleted file mode 100644 index 2f572cf6471..00000000000 --- a/Neos.Fusion/Classes/Core/HttpResponseConstraints.php +++ /dev/null @@ -1,110 +0,0 @@ -partialResponse = new Response(); - } - - /** - * @deprecated - */ - public function setAndMergeFromActionResponse(ActionResponse $actionResponse) - { - $this->partialResponse = $this->applyToResponse($actionResponse->buildHttpResponse()); - } - - /** - * Gets the response status code. - * - * @return int Status code. - */ - public function getStatusCode() - { - return $this->partialResponse->getStatusCode(); - } - - /** - * @param int $code The 3-digit integer result code to set. - */ - public function setStatus(int $code) - { - $this->partialResponse = $this->partialResponse->withStatus($code); - } - - /** - * Retrieves all message header values. - * - * While header names are not case-sensitive, getHeaders() will preserve the - * exact case in which headers were originally specified. - * - * @return string[][] Returns an associative array of the message's headers. Each - * key MUST be a header name, and each value MUST be an array of strings - * for that header. - */ - public function getPartialResponse(): ResponseInterface - { - return $this->partialResponse->getHeaders(); - } - - /** - * While header names are case-insensitive, the casing of the header will - * be preserved by this function, and returned from getHeaders(). - * - * @param string $name Case-insensitive header field name. - * @param string|string[] $value Header value(s). - */ - public function setHeader(string $name, $value) - { - $this->partialResponse = $this->partialResponse->withHeader($name, $value); - } - - /** - * Existing values for the specified header will be maintained. The new - * value(s) will be appended to the existing list. If the header did not - * exist previously, it will be added. - * - * @param string $name Case-insensitive header field name to add. - * @param string|string[] $value Header value(s). - */ - public function setAndMergeHeader(string $name, $value) - { - $this->partialResponse = $this->partialResponse->withAddedHeader($name, $value); - } - - /** - * @param string $name Case-insensitive header field name to remove. - */ - public function unsetHeader(string $name) - { - $this->partialResponse = $this->partialResponse->withoutHeader($name); - } - - public function applyToResponse(ResponseInterface $response): ResponseInterface - { - foreach ($this->partialResponse->getHeaders() as $name => $values) { - $response = $response->withAddedHeader($name, $values); - } - - // preserve non 200 status codes that would otherwise be overwritten - if ($this->partialResponse->getStatusCode() !== 200) { - $response = $response->withStatus($this->partialResponse->getStatusCode()); - } - - // reset internal state - $this->partialResponse = new Response(); - - return $response; - } -} diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index b84f4b45fd2..2f685ba18e0 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -114,8 +114,6 @@ class Runtime */ public readonly FusionGlobals $fusionGlobals; - public readonly HttpResponseConstraints $unsafeHttpResponseConstrains; - /** * @var RuntimeConfiguration */ @@ -161,7 +159,6 @@ public function __construct( ); $this->runtimeContentCache = new RuntimeContentCache($this); $this->fusionGlobals = $fusionGlobals; - $this->unsafeHttpResponseConstrains = new HttpResponseConstraints(); } /** @@ -291,13 +288,16 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons { // legacy controller context layer $possibleRequest = $this->fusionGlobals->get('request'); + $legacyActionResponse = null; if ($possibleRequest instanceof ActionRequest) { $uriBuilder = new UriBuilder(); $uriBuilder->setRequest($possibleRequest); $this->controllerContext = new ControllerContext( $possibleRequest, - new ActionResponse(), + // expose action response to be possibly mutated in neos forms or fusion plugins. + // this behaviour is highly internal and deprecated! + $legacyActionResponse = new ActionResponse(), new Arguments([]), $uriBuilder ); @@ -315,23 +315,18 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons $this->popContext(); } - $legacyControllerContextResponseConstraints = new HttpResponseConstraints(); - if ($this->controllerContext) { - $legacyControllerContextResponseConstraints->setAndMergeFromActionResponse($this->controllerContext->getResponse()); - } - $this->controllerContext = null; - /** * parse potential raw http response possibly rendered via "Neos.Fusion:Http.Message" * {@see \Neos\Fusion\FusionObjects\HttpResponseImplementation} */ $outputStringHasHttpPreamble = is_string($output) && str_starts_with($output, 'HTTP/'); if ($outputStringHasHttpPreamble) { - return $this->unsafeHttpResponseConstrains->applyToResponse( - $legacyControllerContextResponseConstraints->applyToResponse( - Message::parseResponse($output) - ) - ); + $response = Message::parseResponse($output); + if ($legacyActionResponse) { + $response = self::applyActionResponseToPsrResponse($legacyActionResponse, $response); + $this->controllerContext = null; + } + return $response; } $stream = match (true) { @@ -341,11 +336,25 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons default => throw new \RuntimeException(sprintf('Cannot render %s into http response body.', get_debug_type($output)), 1706454898) }; - return $this->unsafeHttpResponseConstrains->applyToResponse( - $legacyControllerContextResponseConstraints->applyToResponse( - new Response(body: $stream) - ) - ); + $response = new Response(body: $stream); + if ($legacyActionResponse) { + $response = self::applyActionResponseToPsrResponse($legacyActionResponse, $response); + $this->controllerContext = null; + } + return $response; + } + + private static function applyActionResponseToPsrResponse(ActionResponse $actionResponse, ResponseInterface $response): ResponseInterface + { + $actionResponseAsHttp = $actionResponse->buildHttpResponse(); + foreach ($actionResponseAsHttp->getHeaders() as $name => $values) { + $response = $response->withAddedHeader($name, $values); + } + // preserve non 200 status codes that would otherwise be overwritten + if ($actionResponseAsHttp->getStatusCode() !== 200) { + $response = $response->withStatus($actionResponseAsHttp->getStatusCode()); + } + return $response; } /** diff --git a/Neos.Neos/Classes/Fusion/PluginImplementation.php b/Neos.Neos/Classes/Fusion/PluginImplementation.php index 02c388453f5..c623542afd9 100644 --- a/Neos.Neos/Classes/Fusion/PluginImplementation.php +++ b/Neos.Neos/Classes/Fusion/PluginImplementation.php @@ -166,12 +166,17 @@ public function evaluate(): string $currentContext = $this->runtime->getCurrentContext(); $this->node = $currentContext['node']; $this->documentNode = $currentContext['documentNode']; + $parentResponse = $this->runtime->getControllerContext()->getResponse(); $pluginResponse = new ActionResponse(); $this->dispatcher->dispatch($this->buildPluginRequest(), $pluginResponse); + // We need to make sure to not merge content up into the parent ActionResponse + // because that would break the Fusion HttpResponse. $content = $pluginResponse->getContent(); + $pluginResponse->setContent(''); + + $pluginResponse->mergeIntoParentResponse($parentResponse); - $this->runtime->unsafeHttpResponseConstrains->setAndMergeFromActionResponse($pluginResponse); return $content; } diff --git a/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php b/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php index 90c721757bb..e8aff26d8ea 100644 --- a/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php +++ b/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php @@ -11,7 +11,6 @@ * source code. */ -use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\Uri; use Neos\Flow\Mvc\ActionRequest; use Neos\Flow\Mvc\ActionResponse; @@ -19,8 +18,6 @@ use Neos\Flow\Mvc\Dispatcher; use Neos\Flow\Mvc\RequestInterface; use Neos\Flow\Tests\UnitTestCase; -use Neos\Fusion\Core\FusionConfiguration; -use Neos\Fusion\Core\FusionGlobals; use Neos\Fusion\Core\Runtime; use Neos\Neos\Fusion\PluginImplementation; use PHPUnit\Framework\MockObject\MockObject; @@ -82,7 +79,7 @@ public function setUp(): void $this->mockControllerContext = $this->getMockBuilder(ControllerContext::class)->disableOriginalConstructor()->getMock(); $this->mockControllerContext->method('getRequest')->willReturn($this->mockActionRequest); - $this->mockRuntime = $this->getMockBuilder(Runtime::class)->setConstructorArgs([FusionConfiguration::fromArray([]), FusionGlobals::fromArray([])])->getMock(); + $this->mockRuntime = $this->getMockBuilder(Runtime::class)->disableOriginalConstructor()->getMock(); $this->mockRuntime->method('getControllerContext')->willReturn($this->mockControllerContext); $this->pluginImplementation->_set('runtime', $this->mockRuntime); @@ -99,12 +96,12 @@ public function responseHeadersDataProvider(): array [ 'Plugin response key does already exist in parent with same value', ['parent' => ['key' => 'value'], 'plugin' => ['key' => 'value']], - ['key' => 'value'] // 'value, value' + ['key' => 'value'] ], [ 'Plugin response key does not exist in parent with different value', ['parent' => ['key' => 'value'], 'plugin' => ['key' => 'otherValue']], - ['key' => 'otherValue'] // 'otherValue, value' + ['key' => 'otherValue'] ], [ 'Plugin response key does not exist in parent', @@ -122,8 +119,6 @@ public function responseHeadersDataProvider(): array */ public function evaluateSetHeaderIntoParent(string $message, array $input, array $expected): void { - $this->markTestSkipped('DOESNT WORK.'); - $this->pluginImplementation->method('buildPluginRequest')->willReturn($this->mockActionRequest); $parentResponse = new ActionResponse(); @@ -138,16 +133,8 @@ public function evaluateSetHeaderIntoParent(string $message, array $input, array $this->pluginImplementation->evaluate(); - // in the runtime would be: - $runtimeResponse = $this->mockRuntime->unsafeHttpResponseConstrains->applyToResponse(new Response()); - - // in the action would be: - $parentResponse->replaceHttpResponse($runtimeResponse); - foreach ($expected as $expectedKey => $expectedValue) { - // previously tests succeeded: - // self::assertEquals($expectedValue, join(', ', \Neos\Utility\ObjectAccess::getProperty($parentResponse, 'headers', true)[$expectedKey]), $message); - self::assertEquals($expectedValue, $parentResponse->buildHttpResponse()->getHeaderLine($expectedKey), $message); + self::assertEquals($expectedValue, (string)$parentResponse->getHttpHeader($expectedKey), $message); } } From cc70fedd9ac9ed21139f658a7746689dd76475ce Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 30 Jan 2024 15:26:04 +0100 Subject: [PATCH 046/214] TASK: Migrate further fusion objects to `$this->runtime->fusionGlobals->get('request');` --- Neos.Fusion/Classes/Core/Runtime.php | 20 +++++++++---------- .../ResourceUriImplementation.php | 10 ++++++---- .../ResourceUriImplementationTest.php | 18 ++++++----------- .../Classes/Fusion/ImageUriImplementation.php | 5 ++++- .../Classes/Fusion/NodeUriImplementation.php | 17 ++++++++++++++-- .../Classes/Fusion/PluginImplementation.php | 5 ++++- 6 files changed, 45 insertions(+), 30 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 2f685ba18e0..3f83ac5cb82 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -303,6 +303,7 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons ); } + /** unlike pushContextArray, we will only allow "legal" fusion global variables. {@see self::pushContext} */ foreach ($contextArray as $key => $_) { if ($this->fusionGlobals->has($key)) { throw new Exception(sprintf('Overriding Fusion global variable "%s" via @context is not allowed.', $key), 1706452063); @@ -323,7 +324,7 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons if ($outputStringHasHttpPreamble) { $response = Message::parseResponse($output); if ($legacyActionResponse) { - $response = self::applyActionResponseToPsrResponse($legacyActionResponse, $response); + $response = self::applyActionResponseToHttpResponse($legacyActionResponse, $response); $this->controllerContext = null; } return $response; @@ -338,23 +339,22 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons $response = new Response(body: $stream); if ($legacyActionResponse) { - $response = self::applyActionResponseToPsrResponse($legacyActionResponse, $response); + $response = self::applyActionResponseToHttpResponse($legacyActionResponse, $response); $this->controllerContext = null; } return $response; } - private static function applyActionResponseToPsrResponse(ActionResponse $actionResponse, ResponseInterface $response): ResponseInterface + private static function applyActionResponseToHttpResponse(ActionResponse $actionResponse, ResponseInterface $httpResponse): ResponseInterface { - $actionResponseAsHttp = $actionResponse->buildHttpResponse(); - foreach ($actionResponseAsHttp->getHeaders() as $name => $values) { - $response = $response->withAddedHeader($name, $values); + foreach ($actionResponse->buildHttpResponse()->getHeaders() as $name => $values) { + $httpResponse = $httpResponse->withAddedHeader($name, $values); } - // preserve non 200 status codes that would otherwise be overwritten - if ($actionResponseAsHttp->getStatusCode() !== 200) { - $response = $response->withStatus($actionResponseAsHttp->getStatusCode()); + // if the status code is 200 we assume it's the default and will not overrule it + if ($actionResponse->getStatusCode() !== 200) { + $httpResponse = $httpResponse->withStatus($actionResponse->getStatusCode()); } - return $response; + return $httpResponse; } /** diff --git a/Neos.Fusion/Classes/FusionObjects/ResourceUriImplementation.php b/Neos.Fusion/Classes/FusionObjects/ResourceUriImplementation.php index 06804001fa0..ce4603163db 100644 --- a/Neos.Fusion/Classes/FusionObjects/ResourceUriImplementation.php +++ b/Neos.Fusion/Classes/FusionObjects/ResourceUriImplementation.php @@ -116,10 +116,12 @@ public function evaluate() } else { $package = $this->getPackage(); if ($package === null) { - $controllerContext = $this->runtime->getControllerContext(); - /** @var $actionRequest ActionRequest */ - $actionRequest = $controllerContext->getRequest(); - $package = $actionRequest->getControllerPackageKey(); + $possibleRequest = $this->runtime->fusionGlobals->get('request'); + if ($possibleRequest instanceof ActionRequest) { + $package = $possibleRequest->getControllerPackageKey(); + } else { + throw new \RuntimeException('Could not infer package-key from action request. Please render Fusion with request or specify a package-key.', 1706624314); + } } } $localize = $this->isLocalize(); diff --git a/Neos.Fusion/Tests/Unit/FusionObjects/ResourceUriImplementationTest.php b/Neos.Fusion/Tests/Unit/FusionObjects/ResourceUriImplementationTest.php index 742f53d1cc9..a0cc8983cb1 100644 --- a/Neos.Fusion/Tests/Unit/FusionObjects/ResourceUriImplementationTest.php +++ b/Neos.Fusion/Tests/Unit/FusionObjects/ResourceUriImplementationTest.php @@ -13,10 +13,11 @@ use Neos\Flow\I18n\Service; use Neos\Flow\Mvc\ActionRequest; -use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Flow\ResourceManagement\PersistentResource; use Neos\Flow\ResourceManagement\ResourceManager; use Neos\Flow\Tests\UnitTestCase; +use Neos\Fusion\Core\FusionConfiguration; +use Neos\Fusion\Core\FusionGlobals; use Neos\Fusion\Core\Runtime; use Neos\Fusion\Exception; use Neos\Fusion\FusionObjects\ResourceUriImplementation; @@ -46,11 +47,6 @@ class ResourceUriImplementationTest extends UnitTestCase */ protected $mockI18nService; - /** - * @var ControllerContext - */ - protected $mockControllerContext; - /** * @var ActionRequest */ @@ -58,14 +54,12 @@ class ResourceUriImplementationTest extends UnitTestCase public function setUp(): void { - $this->mockRuntime = $this->getMockBuilder(Runtime::class)->disableOriginalConstructor()->getMock(); - - $this->mockControllerContext = $this->getMockBuilder(ControllerContext::class)->disableOriginalConstructor()->getMock(); - $this->mockActionRequest = $this->getMockBuilder(ActionRequest::class)->disableOriginalConstructor()->getMock(); - $this->mockControllerContext->expects(self::any())->method('getRequest')->will(self::returnValue($this->mockActionRequest)); - $this->mockRuntime->expects(self::any())->method('getControllerContext')->will(self::returnValue($this->mockControllerContext)); + $this->mockRuntime = $this->getMockBuilder(Runtime::class)->setConstructorArgs([ + FusionConfiguration::fromArray([]), + FusionGlobals::fromArray(['request' => $this->mockActionRequest]) + ])->getMock(); $this->resourceUriImplementation = new ResourceUriImplementation($this->mockRuntime, 'resourceUri/test', 'Neos.Fusion:ResourceUri'); diff --git a/Neos.Neos/Classes/Fusion/ImageUriImplementation.php b/Neos.Neos/Classes/Fusion/ImageUriImplementation.php index 506fb8296f4..263861658a5 100644 --- a/Neos.Neos/Classes/Fusion/ImageUriImplementation.php +++ b/Neos.Neos/Classes/Fusion/ImageUriImplementation.php @@ -15,6 +15,7 @@ namespace Neos\Neos\Fusion; use Neos\Flow\Annotations as Flow; +use Neos\Flow\Mvc\ActionRequest; use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Model\ThumbnailConfiguration; use Neos\Media\Domain\Service\AssetService; @@ -181,7 +182,9 @@ public function evaluate() $this->getFormat() ); } - $request = $this->getRuntime()->getControllerContext()->getRequest(); + + $possibleRequest = $this->runtime->fusionGlobals->get('request'); + $request = $possibleRequest instanceof ActionRequest ? $possibleRequest : null; $thumbnailData = $this->assetService->getThumbnailUriAndSizeForAsset($asset, $thumbnailConfiguration, $request); if ($thumbnailData === null) { return ''; diff --git a/Neos.Neos/Classes/Fusion/NodeUriImplementation.php b/Neos.Neos/Classes/Fusion/NodeUriImplementation.php index 7845fdbcaed..9e1da3c7d83 100644 --- a/Neos.Neos/Classes/Fusion/NodeUriImplementation.php +++ b/Neos.Neos/Classes/Fusion/NodeUriImplementation.php @@ -14,14 +14,16 @@ namespace Neos\Neos\Fusion; +use GuzzleHttp\Psr7\ServerRequest; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\Flow\Mvc\ActionRequest; +use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\Log\Utility\LogEnvironment; use Neos\Flow\Mvc\Exception\NoMatchingRouteException; use Neos\Flow\Mvc\Routing\UriBuilder; use Neos\Fusion\FusionObjects\AbstractFusionObject; -use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\Neos\FrontendRouting\NodeUriBuilder; use Psr\Log\LoggerInterface; @@ -159,7 +161,18 @@ public function evaluate() );*/ $uriBuilder = new UriBuilder(); - $uriBuilder->setRequest($this->runtime->getControllerContext()->getRequest()); + $possibleRequest = $this->runtime->fusionGlobals->get('request'); + if ($possibleRequest instanceof ActionRequest) { + $uriBuilder->setRequest($possibleRequest); + } else { + // unfortunately, the uri-builder always needs a request at hand and cannot build uris without + // even, if the default param merging would not be required + // this will improve with a reformed uri building: + // https://github.com/neos/flow-development-collection/pull/2744 + $uriBuilder->setRequest( + ActionRequest::fromHttpRequest(ServerRequest::fromGlobals()) + ); + } $uriBuilder ->setFormat($this->getFormat()) ->setCreateAbsoluteUri($this->isAbsolute()) diff --git a/Neos.Neos/Classes/Fusion/PluginImplementation.php b/Neos.Neos/Classes/Fusion/PluginImplementation.php index c623542afd9..36ba957cc32 100644 --- a/Neos.Neos/Classes/Fusion/PluginImplementation.php +++ b/Neos.Neos/Classes/Fusion/PluginImplementation.php @@ -90,7 +90,10 @@ public function getArgumentNamespace() */ protected function buildPluginRequest(): ActionRequest { - $parentRequest = $this->runtime->getControllerContext()->getRequest(); + $parentRequest = $this->runtime->fusionGlobals->get('request'); + if (!$parentRequest instanceof ActionRequest) { + throw new \RuntimeException('Fusion Plugins must be rendered with an ActionRequest set as fusion-global.', 1706624581); + } $pluginRequest = $parentRequest->createSubRequest(); $pluginRequest->setArgumentNamespace('--' . $this->getPluginNamespace()); $this->passArgumentsToPluginRequest($pluginRequest); From 58cefd695706cef336bfa0819e4578fa77e16c81 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 3 Feb 2024 22:29:25 +0100 Subject: [PATCH 047/214] TASK: Document legacy Runtime::getControllerContext --- Neos.Fusion/Classes/Core/Runtime.php | 104 ++++++++++++++++----------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 3f83ac5cb82..082737ad67e 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -120,9 +120,9 @@ class Runtime protected $runtimeConfiguration; /** - * @deprecated + * @deprecated legacy layer {@see self::getControllerContext()} */ - protected ?ControllerContext $controllerContext = null; + private ?ActionResponse $legacyActionResponseForCurrentRendering = null; /** * @var array @@ -161,22 +161,6 @@ public function __construct( $this->fusionGlobals = $fusionGlobals; } - /** - * Returns the context which has been passed by the currently active MVC Controller - * - * DEPRECATED CONCEPT. We only implement this as backwards-compatible layer. - * - * @deprecated use `Runtime::fusionGlobals->get('request')` instead to get the request. {@see FusionGlobals::get()} - * @internal - */ - public function getControllerContext(): ControllerContext - { - if ($this->controllerContext === null) { - throw new Exception(sprintf('Legacy controller context in runtime is only available when fusion global "request" is a ActionRequest and during "renderResponse".'), 1706458355); - } - return $this->controllerContext; - } - /** * Inject settings of this package * @@ -286,22 +270,11 @@ public function getLastEvaluationStatus() public function renderResponse(string $fusionPath, array $contextArray): ResponseInterface { - // legacy controller context layer - $possibleRequest = $this->fusionGlobals->get('request'); - $legacyActionResponse = null; - if ($possibleRequest instanceof ActionRequest) { - $uriBuilder = new UriBuilder(); - $uriBuilder->setRequest($possibleRequest); - - $this->controllerContext = new ControllerContext( - $possibleRequest, - // expose action response to be possibly mutated in neos forms or fusion plugins. - // this behaviour is highly internal and deprecated! - $legacyActionResponse = new ActionResponse(), - new Arguments([]), - $uriBuilder - ); + if ($this->legacyActionResponseForCurrentRendering !== null) { + throw new Exception('Recursion detected in `Runtime::renderResponse`. This entry point is only allowed to be invoked once per rendering.', 1706993940); } + /** Legacy layer {@see self::getControllerContext()} */ + $this->legacyActionResponseForCurrentRendering = new ActionResponse(); /** unlike pushContextArray, we will only allow "legal" fusion global variables. {@see self::pushContext} */ foreach ($contextArray as $key => $_) { @@ -323,10 +296,8 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons $outputStringHasHttpPreamble = is_string($output) && str_starts_with($output, 'HTTP/'); if ($outputStringHasHttpPreamble) { $response = Message::parseResponse($output); - if ($legacyActionResponse) { - $response = self::applyActionResponseToHttpResponse($legacyActionResponse, $response); - $this->controllerContext = null; - } + $response = self::applyActionResponseToHttpResponse($this->legacyActionResponseForCurrentRendering, $response); + $this->legacyActionResponseForCurrentRendering = null; return $response; } @@ -338,10 +309,8 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons }; $response = new Response(body: $stream); - if ($legacyActionResponse) { - $response = self::applyActionResponseToHttpResponse($legacyActionResponse, $response); - $this->controllerContext = null; - } + $response = self::applyActionResponseToHttpResponse($this->legacyActionResponseForCurrentRendering, $response); + $this->legacyActionResponseForCurrentRendering = null; return $response; } @@ -988,6 +957,59 @@ protected function throwExceptionForUnrenderablePathIfNeeded($fusionPath, $fusio } } + /** + * The concept of the controller context inside Fusion has been deprecated. + * + * To migrate the use case of fetching the active request, please look into {@see FusionGlobals::get()} instead. + * By convention, an {@see ActionRequest} will be available as `request`: + * + * ```php + * $actionRequest = $this->runtime->fusionGlobals->get('request'); + * if (!$actionRequest instanceof ActionRequest) { + * // fallback or error + * } + * ``` + * + * To get an {@see UriBuilder} proceed with: + * + * ```php + * $uriBuilder = new UriBuilder(); + * $uriBuilder->setRequest($actionRequest); + * ``` + * + * WARNING: + * Invoking this backwards-compatible layer is possibly unsafe, if the rendering was not started + * in {@see self::renderResponse()} or no `request` global is available. This will raise an exception. + * + * MAINTAINER NOTE: + * Initially it was possible to mutate the current response of the active MVC controller though $response. + * While HIGHLY internal behaviour and ONLY to be used by Neos.Fusion.Form or Neos.Neos:Plugin + * a legacy layer in place still allows this functionality. + * + * @deprecated with Neos 9.0 + * @internal + */ + public function getControllerContext(): ControllerContext + { + // legacy controller context layer + $actionRequest = $this->fusionGlobals->get('request'); + if ($this->legacyActionResponseForCurrentRendering === null || !$actionRequest instanceof ActionRequest) { + throw new Exception(sprintf('Fusions simulated legacy controller context is only available inside `Runtime::renderResponse` and when the Fusion global "request" is an ActionRequest.'), 1706458355); + } + + $uriBuilder = new UriBuilder(); + $uriBuilder->setRequest($actionRequest); + + return new ControllerContext( + $actionRequest, + // expose action response to be possibly mutated in neos forms or fusion plugins. + // this behaviour is highly internal and deprecated! + $this->legacyActionResponseForCurrentRendering, + new Arguments([]), + $uriBuilder + ); + } + /** * Configures this runtime to override the default exception handler configured in the settings * or via Fusion's \@exceptionHandler {@see AbstractRenderingExceptionHandler}. From 7559f87b1cf5011885c06f819282064f09a177b4 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 3 Feb 2024 22:41:59 +0100 Subject: [PATCH 048/214] TASK: Extract controller context legacy layer into withSimulatedLegacyControllerContext --- Neos.Fusion/Classes/Core/Runtime.php | 105 ++++++++++++++------------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 082737ad67e..f3f597a5ef0 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -270,60 +270,39 @@ public function getLastEvaluationStatus() public function renderResponse(string $fusionPath, array $contextArray): ResponseInterface { - if ($this->legacyActionResponseForCurrentRendering !== null) { - throw new Exception('Recursion detected in `Runtime::renderResponse`. This entry point is only allowed to be invoked once per rendering.', 1706993940); - } - /** Legacy layer {@see self::getControllerContext()} */ - $this->legacyActionResponseForCurrentRendering = new ActionResponse(); - - /** unlike pushContextArray, we will only allow "legal" fusion global variables. {@see self::pushContext} */ + /** Unlike pushContextArray, we don't allow to overrule fusion globals {@see self::pushContext} */ foreach ($contextArray as $key => $_) { if ($this->fusionGlobals->has($key)) { throw new Exception(sprintf('Overriding Fusion global variable "%s" via @context is not allowed.', $key), 1706452063); } } $this->pushContextArray($contextArray); - try { - $output = $this->render($fusionPath); - } finally { - $this->popContext(); - } - /** - * parse potential raw http response possibly rendered via "Neos.Fusion:Http.Message" - * {@see \Neos\Fusion\FusionObjects\HttpResponseImplementation} - */ - $outputStringHasHttpPreamble = is_string($output) && str_starts_with($output, 'HTTP/'); - if ($outputStringHasHttpPreamble) { - $response = Message::parseResponse($output); - $response = self::applyActionResponseToHttpResponse($this->legacyActionResponseForCurrentRendering, $response); - $this->legacyActionResponseForCurrentRendering = null; - return $response; - } + return $this->withSimulatedLegacyControllerContext(function () use ($fusionPath) { + try { + $output = $this->render($fusionPath); + } finally { + $this->popContext(); + } - $stream = match (true) { - is_string($output), - $output instanceof \Stringable => Utils::streamFor((string)$output), - $output === null, $output === false => Utils::streamFor(''), - default => throw new \RuntimeException(sprintf('Cannot render %s into http response body.', get_debug_type($output)), 1706454898) - }; + /** + * parse potential raw http response possibly rendered via "Neos.Fusion:Http.Message" + * {@see \Neos\Fusion\FusionObjects\HttpResponseImplementation} + */ + $outputStringHasHttpPreamble = is_string($output) && str_starts_with($output, 'HTTP/'); + if ($outputStringHasHttpPreamble) { + return Message::parseResponse($output); + } - $response = new Response(body: $stream); - $response = self::applyActionResponseToHttpResponse($this->legacyActionResponseForCurrentRendering, $response); - $this->legacyActionResponseForCurrentRendering = null; - return $response; - } + $stream = match (true) { + is_string($output), + $output instanceof \Stringable => Utils::streamFor((string)$output), + $output === null, $output === false => Utils::streamFor(''), + default => throw new \RuntimeException(sprintf('Cannot render %s into http response body.', get_debug_type($output)), 1706454898) + }; - private static function applyActionResponseToHttpResponse(ActionResponse $actionResponse, ResponseInterface $httpResponse): ResponseInterface - { - foreach ($actionResponse->buildHttpResponse()->getHeaders() as $name => $values) { - $httpResponse = $httpResponse->withAddedHeader($name, $values); - } - // if the status code is 200 we assume it's the default and will not overrule it - if ($actionResponse->getStatusCode() !== 200) { - $httpResponse = $httpResponse->withStatus($actionResponse->getStatusCode()); - } - return $httpResponse; + return new Response(body: $stream); + }); } /** @@ -957,6 +936,39 @@ protected function throwExceptionForUnrenderablePathIfNeeded($fusionPath, $fusio } } + /** + * Implements the legacy controller context simulation {@see self::getControllerContext()} + * + * Initially it was possible to mutate the current response of the active MVC controller though $response. + * While HIGHLY internal behaviour and ONLY to be used by Neos.Fusion.Form or Neos.Neos:Plugin + * this legacy layer is in place still allows this functionality. + * + * @param \Closure(): ResponseInterface $renderer + */ + private function withSimulatedLegacyControllerContext(\Closure $renderer): ResponseInterface + { + if ($this->legacyActionResponseForCurrentRendering !== null) { + throw new Exception('Recursion detected in `Runtime::renderResponse`. This entry point is only allowed to be invoked once per rendering.', 1706993940); + } + $this->legacyActionResponseForCurrentRendering = new ActionResponse(); + + // actual rendering + $httpResponse = $renderer(); + + // transfer possible headers that have been set dynamically + foreach ($this->legacyActionResponseForCurrentRendering->buildHttpResponse()->getHeaders() as $name => $values) { + $httpResponse = $httpResponse->withAddedHeader($name, $values); + } + // if the status code is 200 we assume it's the default and will not overrule it + if ($this->legacyActionResponseForCurrentRendering->getStatusCode() !== 200) { + $httpResponse = $httpResponse->withStatus($this->legacyActionResponseForCurrentRendering->getStatusCode()); + } + + // reset for next render + $this->legacyActionResponseForCurrentRendering = null; + return $httpResponse; + } + /** * The concept of the controller context inside Fusion has been deprecated. * @@ -981,11 +993,6 @@ protected function throwExceptionForUnrenderablePathIfNeeded($fusionPath, $fusio * Invoking this backwards-compatible layer is possibly unsafe, if the rendering was not started * in {@see self::renderResponse()} or no `request` global is available. This will raise an exception. * - * MAINTAINER NOTE: - * Initially it was possible to mutate the current response of the active MVC controller though $response. - * While HIGHLY internal behaviour and ONLY to be used by Neos.Fusion.Form or Neos.Neos:Plugin - * a legacy layer in place still allows this functionality. - * * @deprecated with Neos 9.0 * @internal */ From 35f688de64d367952b0a957b7e01460175996d72 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 16 Feb 2024 22:41:56 +0100 Subject: [PATCH 049/214] TASK: Runtime fix `renderResponse` lock not being released --- Neos.Fusion/Classes/Core/Runtime.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index f3f597a5ef0..66df6e0196f 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -953,19 +953,23 @@ private function withSimulatedLegacyControllerContext(\Closure $renderer): Respo $this->legacyActionResponseForCurrentRendering = new ActionResponse(); // actual rendering - $httpResponse = $renderer(); + try { + $httpResponse = $renderer(); + } finally { + $toBeMergedLegacyActionResponse = $this->legacyActionResponseForCurrentRendering; + // reset for next render + $this->legacyActionResponseForCurrentRendering = null; + } // transfer possible headers that have been set dynamically - foreach ($this->legacyActionResponseForCurrentRendering->buildHttpResponse()->getHeaders() as $name => $values) { + foreach ($toBeMergedLegacyActionResponse->buildHttpResponse()->getHeaders() as $name => $values) { $httpResponse = $httpResponse->withAddedHeader($name, $values); } // if the status code is 200 we assume it's the default and will not overrule it - if ($this->legacyActionResponseForCurrentRendering->getStatusCode() !== 200) { - $httpResponse = $httpResponse->withStatus($this->legacyActionResponseForCurrentRendering->getStatusCode()); + if ($toBeMergedLegacyActionResponse->getStatusCode() !== 200) { + $httpResponse = $httpResponse->withStatus($toBeMergedLegacyActionResponse->getStatusCode()); } - // reset for next render - $this->legacyActionResponseForCurrentRendering = null; return $httpResponse; } From 8adfc90c8e77e67ac7279b3cc823d958865cdd17 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 16 Feb 2024 22:47:21 +0100 Subject: [PATCH 050/214] TASK: Runtime `renderResponse` unwrap `RuntimeException` itself Previously the pattern was that the utmost caller of the runtime would unwrap the exception. To simplify this, as the runtime now has a single entry point, we add this behaviour into the runtime. --- Neos.Fusion/Classes/Core/Runtime.php | 3 +++ Neos.Fusion/Classes/View/FusionView.php | 6 +----- Neos.Neos/Classes/View/FusionView.php | 16 +++++----------- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 66df6e0196f..266c101082d 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -281,6 +281,9 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons return $this->withSimulatedLegacyControllerContext(function () use ($fusionPath) { try { $output = $this->render($fusionPath); + } catch (RuntimeException $exception) { + // unwrap the FusionRuntimeException + throw $exception->getPrevious(); } finally { $this->popContext(); } diff --git a/Neos.Fusion/Classes/View/FusionView.php b/Neos.Fusion/Classes/View/FusionView.php index bdc4413e113..36d4fe6ae5c 100644 --- a/Neos.Fusion/Classes/View/FusionView.php +++ b/Neos.Fusion/Classes/View/FusionView.php @@ -149,11 +149,7 @@ public function render() $this->initializeFusionRuntime(); if ($this->getOption('renderHttpResponse') === true) { - try { - return $this->fusionRuntime->renderResponse($this->getFusionPathForCurrentRequest(), $this->variables); - } catch (RuntimeException $exception) { - throw $exception->getPrevious(); - } + return $this->fusionRuntime->renderResponse($this->getFusionPathForCurrentRequest(), $this->variables); } else { try { $this->fusionRuntime->pushContextArray($this->variables); diff --git a/Neos.Neos/Classes/View/FusionView.php b/Neos.Neos/Classes/View/FusionView.php index 82e29a29634..446771cae37 100644 --- a/Neos.Neos/Classes/View/FusionView.php +++ b/Neos.Neos/Classes/View/FusionView.php @@ -14,7 +14,6 @@ namespace Neos\Neos\View; -use GuzzleHttp\Psr7\Message; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; @@ -24,7 +23,6 @@ use Neos\Fusion\Core\FusionGlobals; use Neos\Fusion\Core\Runtime; use Neos\Fusion\Core\RuntimeFactory; -use Neos\Fusion\Exception\RuntimeException; use Neos\Neos\Domain\Model\RenderingMode; use Neos\Neos\Domain\Repository\SiteRepository; use Neos\Neos\Domain\Service\FusionService; @@ -76,15 +74,11 @@ public function render(): ResponseInterface $this->setFallbackRuleFromDimension($currentNode->subgraphIdentity->dimensionSpacePoint); - try { - return $fusionRuntime->renderResponse($this->fusionPath, [ - 'node' => $currentNode, - 'documentNode' => $this->getClosestDocumentNode($currentNode) ?: $currentNode, - 'site' => $currentSiteNode - ]); - } catch (RuntimeException $exception) { - throw $exception->getPrevious() ?: $exception; - } + return $fusionRuntime->renderResponse($this->fusionPath, [ + 'node' => $currentNode, + 'documentNode' => $this->getClosestDocumentNode($currentNode) ?: $currentNode, + 'site' => $currentSiteNode + ]); } /** From 05aefc7baa260d2d9ae37845afef7912089262a3 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 18 Feb 2024 12:15:58 +0100 Subject: [PATCH 051/214] TASK: Add Fusion RuntimeException::getWrappedException ... to centralise tricking phpstan --- Neos.Fusion/Classes/Core/Runtime.php | 3 +-- Neos.Fusion/Classes/Exception/RuntimeException.php | 9 +++++++++ Neos.Fusion/Classes/View/FusionView.php | 2 +- Neos.Neos/Classes/View/FusionExceptionView.php | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 266c101082d..2d44aaac02e 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -282,8 +282,7 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons try { $output = $this->render($fusionPath); } catch (RuntimeException $exception) { - // unwrap the FusionRuntimeException - throw $exception->getPrevious(); + throw $exception->getWrappedException(); } finally { $this->popContext(); } diff --git a/Neos.Fusion/Classes/Exception/RuntimeException.php b/Neos.Fusion/Classes/Exception/RuntimeException.php index 8ee31394250..b827edf4b74 100644 --- a/Neos.Fusion/Classes/Exception/RuntimeException.php +++ b/Neos.Fusion/Classes/Exception/RuntimeException.php @@ -32,4 +32,13 @@ public function getFusionPath() { return $this->fusionPath; } + + /** + * Unwrap this Fusion RuntimeException + */ + public function getWrappedException(): \Exception + { + /** @phpstan-ignore-next-line due to overridden construction, we are sure that the previous exists. */ + return $this->getPrevious(); + } } diff --git a/Neos.Fusion/Classes/View/FusionView.php b/Neos.Fusion/Classes/View/FusionView.php index 36d4fe6ae5c..e44ffb6f664 100644 --- a/Neos.Fusion/Classes/View/FusionView.php +++ b/Neos.Fusion/Classes/View/FusionView.php @@ -155,7 +155,7 @@ public function render() $this->fusionRuntime->pushContextArray($this->variables); return $this->fusionRuntime->render($this->getFusionPathForCurrentRequest()); } catch (RuntimeException $exception) { - throw $exception->getPrevious(); + throw $exception->getWrappedException(); } finally { $this->fusionRuntime->popContext(); } diff --git a/Neos.Neos/Classes/View/FusionExceptionView.php b/Neos.Neos/Classes/View/FusionExceptionView.php index 46241f48729..1d4a85119d9 100644 --- a/Neos.Neos/Classes/View/FusionExceptionView.php +++ b/Neos.Neos/Classes/View/FusionExceptionView.php @@ -171,7 +171,7 @@ public function render() $output = $fusionRuntime->render('error'); return $this->extractBodyFromOutput($output); } catch (RuntimeException $exception) { - throw $exception->getPrevious() ?: $exception; + throw $exception->getWrappedException(); } finally { $fusionRuntime->popContext(); } From 706ccc77d56ba011235263b98ba1498250134509 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 18 Feb 2024 12:26:26 +0100 Subject: [PATCH 052/214] TASK: Remove manual http response parsing from FusionExceptionView --- .../Classes/View/FusionExceptionView.php | 33 +++++-------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/Neos.Neos/Classes/View/FusionExceptionView.php b/Neos.Neos/Classes/View/FusionExceptionView.php index 1d4a85119d9..4feb70ea7c5 100644 --- a/Neos.Neos/Classes/View/FusionExceptionView.php +++ b/Neos.Neos/Classes/View/FusionExceptionView.php @@ -32,7 +32,6 @@ use Neos\Fusion\Core\FusionGlobals; use Neos\Fusion\Core\Runtime as FusionRuntime; use Neos\Fusion\Core\RuntimeFactory; -use Neos\Fusion\Exception\RuntimeException; use Neos\Neos\Domain\Model\RenderingMode; use Neos\Neos\Domain\Repository\DomainRepository; use Neos\Neos\Domain\Repository\SiteRepository; @@ -158,7 +157,7 @@ public function render() $this->setFallbackRuleFromDimension($dimensionSpacePoint); - $fusionRuntime->pushContextArray(array_merge( + $httpResponse = $fusionRuntime->renderResponse('error', array_merge( $this->variables, [ 'node' => $currentSiteNode, @@ -167,29 +166,13 @@ public function render() ] )); - try { - $output = $fusionRuntime->render('error'); - return $this->extractBodyFromOutput($output); - } catch (RuntimeException $exception) { - throw $exception->getWrappedException(); - } finally { - $fusionRuntime->popContext(); - } - } - - /** - * @param string $output - * @return string The message body without the message head - */ - protected function extractBodyFromOutput(string $output): string - { - if (substr($output, 0, 5) === 'HTTP/') { - $endOfHeader = strpos($output, "\r\n\r\n"); - if ($endOfHeader !== false) { - $output = substr($output, $endOfHeader + 4); - } - } - return $output; + /** + * Workaround: The http status code will already be sent and + * Flow's {@see \Neos\Flow\Error\DebugExceptionHandler::echoExceptionWeb()} + * expects a view to return a string to be echo'd. + * Thus, we unwrap the repose here: + */ + return $httpResponse->getBody()->getContents(); } /** From aca97ab02f5abffa9dc5b9f1ebc700a44c284f63 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 18 Feb 2024 15:15:28 +0100 Subject: [PATCH 053/214] TASK: Revert Fusion `FusionView` HttpResponse support This will be discussed separately and not part of the change of the Neos Node `FusionView` --- Neos.Fusion/Classes/View/FusionView.php | 40 +++++++++++-------- .../View/Fixtures/Fusion/Root.fusion | 4 -- .../Tests/Functional/View/FusionViewTest.php | 36 ----------------- 3 files changed, 23 insertions(+), 57 deletions(-) diff --git a/Neos.Fusion/Classes/View/FusionView.php b/Neos.Fusion/Classes/View/FusionView.php index e44ffb6f664..bf983346f4f 100644 --- a/Neos.Fusion/Classes/View/FusionView.php +++ b/Neos.Fusion/Classes/View/FusionView.php @@ -22,7 +22,6 @@ use Neos\Fusion\Core\Runtime; use Neos\Fusion\Core\RuntimeFactory; use Neos\Fusion\Exception\RuntimeException; -use Psr\Http\Message\ResponseInterface; /** * View for using Fusion for standard MVC controllers. @@ -47,8 +46,7 @@ class FusionView extends AbstractView 'fusionGlobals' => [null, 'Additional global variables; merged together with the "request". Must only be specified at creation.', FusionGlobals::class], 'packageKey' => [null, 'The package key where the Fusion should be loaded from. If not given, is automatically derived from the current request.', 'string'], 'debugMode' => [false, 'Flag to enable debug mode of the Fusion runtime explicitly (overriding the global setting).', 'boolean'], - 'enableContentCache' => [false, 'Flag to enable content caching inside Fusion (overriding the global setting).', 'boolean'], - 'renderHttpResponse' => [false, 'Flag to render fusion as http repose for advanced form support and Neos.Fusion:Http.ResponseHead support.', 'boolean'], + 'enableContentCache' => [false, 'Flag to enable content caching inside Fusion (overriding the global setting).', 'boolean'] ]; /** @@ -141,25 +139,13 @@ public function setFusionPathPatterns(array $pathPatterns) /** * Render the view * - * @return mixed|ResponseInterface The rendered view + * @return mixed The rendered view * @api */ public function render() { $this->initializeFusionRuntime(); - - if ($this->getOption('renderHttpResponse') === true) { - return $this->fusionRuntime->renderResponse($this->getFusionPathForCurrentRequest(), $this->variables); - } else { - try { - $this->fusionRuntime->pushContextArray($this->variables); - return $this->fusionRuntime->render($this->getFusionPathForCurrentRequest()); - } catch (RuntimeException $exception) { - throw $exception->getWrappedException(); - } finally { - $this->fusionRuntime->popContext(); - } - } + return $this->renderFusion(); } /** @@ -190,6 +176,9 @@ public function initializeFusionRuntime() $this->parsedFusion, $fusionGlobals ); + if (isset($this->controllerContext)) { + $this->fusionRuntime->setControllerContext($this->controllerContext); + } } if (isset($this->options['debugMode'])) { $this->fusionRuntime->setDebugMode($this->options['debugMode']); @@ -294,4 +283,21 @@ protected function getFusionPathForCurrentRequest() } return $this->fusionPath; } + + /** + * Render the given Fusion and return the rendered page + * @return mixed + * @throws \Exception + */ + protected function renderFusion() + { + $this->fusionRuntime->pushContextArray($this->variables); + try { + $output = $this->fusionRuntime->render($this->getFusionPathForCurrentRequest()); + } catch (RuntimeException $exception) { + throw $exception->getPrevious(); + } + $this->fusionRuntime->popContext(); + return $output; + } } diff --git a/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/Root.fusion b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/Root.fusion index fbcf0fc40a3..b190f6bd5a0 100644 --- a/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/Root.fusion +++ b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/Root.fusion @@ -1,5 +1 @@ include: ./**/*.fusion -include: 'resource://Neos.Fusion/Private/Fusion/Prototypes/Join.fusion' -include: 'resource://Neos.Fusion/Private/Fusion/Prototypes/DataStructure.fusion' -include: 'resource://Neos.Fusion/Private/Fusion/Prototypes/Http.Message.fusion' -include: 'resource://Neos.Fusion/Private/Fusion/Prototypes/Http.ResponseHead.fusion' diff --git a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php index 629508c8643..dc2f06d8ba9 100644 --- a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php +++ b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php @@ -15,7 +15,6 @@ use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Flow\Tests\FunctionalTestCase; use Neos\Fusion\View\FusionView; -use Psr\Http\Message\ResponseInterface; /** * Testcase for the Fusion View @@ -65,41 +64,6 @@ public function fusionViewOutputsVariable() self::assertEquals('XHallo Welt', $view->render()); } - /** - * @test - */ - public function fusionViewCanReturnHttpResponse() - { - $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); - $view->setOption('renderHttpResponse', true); - $view->assign('test', 'Hallo Welt'); - $response = $view->render(); - self::assertInstanceOf(ResponseInterface::class, $response); - self::assertEquals('XHallo Welt', $view->render()->getBody()->getContents()); - } - - /** - * @test - */ - public function fusionViewCanReturnHttpResponseFromHttpMessagePrototype() - { - $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); - $view->setFusionPath('response'); - self::assertSame(<<render()); - - $view->setOption('renderHttpResponse', true); - $response = $view->render(); - self::assertInstanceOf(ResponseInterface::class, $response); - self::assertSame('{"some":"json"}', $response->getBody()->getContents()); - self::assertSame(404, $response->getStatusCode()); - self::assertSame("application/json", $response->getHeaderLine("Content-Type")); - } - /** * Prepare a FusionView for testing that Mocks a request with the given controller and action names. * From 8a53df6ab24452d29d279c25d811bbbb7ebc58de Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 18 Feb 2024 15:57:39 +0100 Subject: [PATCH 054/214] TASK: Make Fusion `FusionView` independent of `ControllerContext` --- Neos.Fusion/Classes/View/FusionView.php | 58 ++++++++++++++----- .../AbstractFusionObjectTest.php | 23 ++------ .../FusionObjects/ContentCacheTest.php | 7 +-- .../Tests/Functional/View/FusionViewTest.php | 18 +----- 4 files changed, 52 insertions(+), 54 deletions(-) diff --git a/Neos.Fusion/Classes/View/FusionView.php b/Neos.Fusion/Classes/View/FusionView.php index bf983346f4f..1d300f57f49 100644 --- a/Neos.Fusion/Classes/View/FusionView.php +++ b/Neos.Fusion/Classes/View/FusionView.php @@ -13,6 +13,7 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\ActionRequest; +use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Flow\Mvc\View\AbstractView; use Neos\Fusion\Core\FusionConfiguration; use Neos\Fusion\Core\FusionGlobals; @@ -94,6 +95,28 @@ public function setOption($optionName, $value) parent::setOption($optionName, $value); } + /** + * Legacy layer to make the `request` available in Fusion. + * + * Please use {@see setOption} and {@see FusionGlobals} instead: + * + * $view->setOption('fusionGlobals', FusionGlobals::fromArray(['request' => $this->request])) + * + * @deprecated with Neos 9 + */ + public function setControllerContext(ControllerContext $controllerContext) + { + /** @var FusionGlobals $fusionGlobals */ + $fusionGlobals = $this->options['fusionGlobals'] ?? FusionGlobals::empty(); + if ($fusionGlobals->has('request')) { + // no need for legacy layer as the request was already set. + return; + } + $this->setOption('fusionGlobals', $fusionGlobals->merge(FusionGlobals::fromArray( + ['request' => $controllerContext->getRequest()] + ))); + } + /** * Sets the Fusion path to be rendered to an explicit value; * to be used mostly inside tests. @@ -164,21 +187,10 @@ public function initializeFusionRuntime() if (!$fusionGlobals instanceof FusionGlobals) { throw new \InvalidArgumentException('View option "fusionGlobals" must be of type FusionGlobals', 1694252923947); } - $fusionGlobals = $fusionGlobals->merge( - FusionGlobals::fromArray( - array_filter([ - 'request' => $this->controllerContext?->getRequest() - ]) - ) - ); - $this->fusionRuntime = $this->runtimeFactory->createFromConfiguration( $this->parsedFusion, $fusionGlobals ); - if (isset($this->controllerContext)) { - $this->fusionRuntime->setControllerContext($this->controllerContext); - } } if (isset($this->options['debugMode'])) { $this->fusionRuntime->setDebugMode($this->options['debugMode']); @@ -250,9 +262,9 @@ protected function getPackageKey() if ($packageKey !== null) { return $packageKey; } else { - $request = $this->controllerContext?->getRequest(); + $request = $this->getRequestFromFusionGlobals(); if (!$request) { - throw new \RuntimeException(sprintf('To resolve the @package in all fusionPathPatterns, either packageKey has to be specified, or the current request be available.')); + throw new \RuntimeException(sprintf('To resolve the @package in all fusionPathPatterns, either packageKey has to be specified, or the current request be available.'), 1708267874); } return $request->getControllerPackageKey(); } @@ -270,8 +282,10 @@ protected function getFusionPathForCurrentRequest() if ($fusionPath !== null) { $this->fusionPath = $fusionPath; } else { - /** @var $request ActionRequest */ - $request = $this->controllerContext->getRequest(); + $request = $this->getRequestFromFusionGlobals(); + if (!$request) { + throw new \RuntimeException(sprintf('The option `fusionPath` was not set. Could not fallback to the current request.'), 1708267857); + } $fusionPathForCurrentRequest = $request->getControllerObjectName(); $fusionPathForCurrentRequest = str_replace('\\Controller\\', '\\', $fusionPathForCurrentRequest); $fusionPathForCurrentRequest = str_replace('\\', '/', $fusionPathForCurrentRequest); @@ -300,4 +314,18 @@ protected function renderFusion() $this->fusionRuntime->popContext(); return $output; } + + private function getRequestFromFusionGlobals(): ?ActionRequest + { + /** @var FusionGlobals $fusionGlobals */ + $fusionGlobals = $this->options['fusionGlobals'] ?? FusionGlobals::empty(); + if (!$fusionGlobals instanceof FusionGlobals) { + throw new \InvalidArgumentException('View option "fusionGlobals" must be of type FusionGlobals', 1694252923947); + } + $actionRequest = $fusionGlobals->get('request'); + if (!$actionRequest instanceof ActionRequest) { + return null; + } + return $actionRequest; + } } diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php b/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php index 810452e8a2b..749af8fe021 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php +++ b/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php @@ -12,11 +12,8 @@ */ use Neos\Flow\Mvc\ActionRequest; -use Neos\Flow\Mvc\ActionResponse; -use Neos\Flow\Mvc\Controller\Arguments; -use Neos\Flow\Mvc\Controller\ControllerContext; -use Neos\Flow\Mvc\Routing\UriBuilder; use Neos\Flow\Tests\FunctionalTestCase; +use Neos\Fusion\Core\FusionGlobals; use Neos\Fusion\View\FusionView; use Psr\Http\Message\ServerRequestFactoryInterface; @@ -27,9 +24,9 @@ abstract class AbstractFusionObjectTest extends FunctionalTestCase { /** - * @var ControllerContext + * @var ActionRequest */ - protected $controllerContext; + protected $request; /** * Helper to build a Fusion view object @@ -43,20 +40,10 @@ protected function buildView() /** @var ServerRequestFactoryInterface $httpRequestFactory */ $httpRequestFactory = $this->objectManager->get(ServerRequestFactoryInterface::class); $httpRequest = $httpRequestFactory->createServerRequest('GET', 'http://localhost/'); - $request = ActionRequest::fromHttpRequest($httpRequest); + $this->request = ActionRequest::fromHttpRequest($httpRequest); - $uriBuilder = new UriBuilder(); - $uriBuilder->setRequest($request); - - $this->controllerContext = new ControllerContext( - $request, - new ActionResponse(), - new Arguments([]), - $uriBuilder - ); - - $view->setControllerContext($this->controllerContext); $view->setPackageKey('Neos.Fusion'); + $view->setOption('fusionGlobals', FusionGlobals::fromArray(['request' => $this->request])); $view->setFusionPathPattern(__DIR__ . '/Fixtures/Fusion'); $view->assign('fixtureDirectory', __DIR__ . '/Fixtures/'); diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/ContentCacheTest.php b/Neos.Fusion/Tests/Functional/FusionObjects/ContentCacheTest.php index b98dfc1a503..a6e1c494449 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/ContentCacheTest.php +++ b/Neos.Fusion/Tests/Functional/FusionObjects/ContentCacheTest.php @@ -13,7 +13,6 @@ use Neos\Flow\Cache\CacheManager; use Neos\Cache\Frontend\FrontendInterface; -use Neos\Flow\Mvc\ActionRequest; use Neos\Fusion\Core\Cache\ContentCache; use Neos\Fusion\Tests\Functional\FusionObjects\Fixtures\Model\TestModel; @@ -331,8 +330,7 @@ public function conditionsAreAppliedForUncachedSegment() 'object' => $object ]); - /** @var \Neos\Flow\Mvc\ActionRequest $actionRequest */ - $actionRequest = $this->controllerContext->getRequest(); + $actionRequest = $this->request; $actionRequest->setArgument('currentPage', 1); $firstRenderResult = $view->render(); @@ -729,8 +727,7 @@ public function contextIsCorrectlyEvaluated() $view->assign('someContextVariable', 'prettyUnused'); $view->setFusionPath('contentCache/dynamicWithChangingDiscriminator'); - /** @var ActionRequest $actionRequest */ - $actionRequest = $this->controllerContext->getRequest(); + $actionRequest = $this->request; $actionRequest->setArgument('testArgument', '1'); $firstRenderResult = $view->render(); diff --git a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php index dc2f06d8ba9..1969b7ada0d 100644 --- a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php +++ b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php @@ -12,8 +12,8 @@ */ use Neos\Flow\Mvc\ActionRequest; -use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Flow\Tests\FunctionalTestCase; +use Neos\Fusion\Core\FusionGlobals; use Neos\Fusion\View\FusionView; /** @@ -22,19 +22,6 @@ */ class FusionViewTest extends FunctionalTestCase { - /** - * @var ControllerContext - */ - protected $mockControllerContext; - - /** - * Initializer - */ - public function setUp(): void - { - $this->mockControllerContext = $this->getMockBuilder(ControllerContext::class)->disableOriginalConstructor()->getMock(); - } - /** * @test */ @@ -76,10 +63,9 @@ protected function buildView($controllerObjectName, $controllerActionName) $request = $this->getMockBuilder(ActionRequest::class)->disableOriginalConstructor()->getMock(); $request->expects(self::any())->method('getControllerObjectName')->will(self::returnValue($controllerObjectName)); $request->expects(self::any())->method('getControllerActionName')->will(self::returnValue($controllerActionName)); - $this->mockControllerContext->expects(self::any())->method('getRequest')->will(self::returnValue($request)); $view = new FusionView(); - $view->setControllerContext($this->mockControllerContext); + $view->setOption('fusionGlobals', FusionGlobals::fromArray(['request' => $request])); $view->setFusionPathPattern(__DIR__ . '/Fixtures/Fusion'); return $view; From c0ddd4595f96f9ca65575cc99898046bb3dc2abd Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 22 Feb 2024 19:03:50 +0100 Subject: [PATCH 055/214] !!! TASK: Introduce `LegacyFusionControllerContext` instead of using `ControllerContext` `getArguments` removed without replacement. --- .../Core/LegacyFusionControllerContext.php | 127 ++++++++++++++++++ Neos.Fusion/Classes/Core/Runtime.php | 32 +---- .../Fusion/ExceptionHandlers/PageHandler.php | 11 +- .../Unit/Fusion/PluginImplementationTest.php | 2 + 4 files changed, 144 insertions(+), 28 deletions(-) create mode 100644 Neos.Fusion/Classes/Core/LegacyFusionControllerContext.php diff --git a/Neos.Fusion/Classes/Core/LegacyFusionControllerContext.php b/Neos.Fusion/Classes/Core/LegacyFusionControllerContext.php new file mode 100644 index 00000000000..07d02d0e6f1 --- /dev/null +++ b/Neos.Fusion/Classes/Core/LegacyFusionControllerContext.php @@ -0,0 +1,127 @@ +runtime->fusionGlobals->get('request'); + * if (!$actionRequest instanceof ActionRequest) { + * // fallback or error + * } + * + * To get an {@see UriBuilder} proceed with: + * + * $uriBuilder = new UriBuilder(); + * $uriBuilder->setRequest($actionRequest); + * + * WARNING regarding {@see Runtime::getControllerContext()}: + * Invoking this backwards-compatible layer is possibly unsafe, if the rendering was not started + * in {@see self::renderResponse()} or no `request` global is available. This will raise an exception. + * + * @deprecated with Neos 9.0 can be removed with 10 + * @internal + */ +final class LegacyFusionControllerContext +{ + /** + * @Flow\Inject + * @var FlashMessageService + */ + protected $flashMessageService; + + public function __construct( + private readonly ActionRequest $request, + private readonly ActionResponse $legacyActionResponseForCurrentRendering + ) { + } + + /** + * To migrate the use case of fetching the active request, please look into {@see FusionGlobals::get()} instead. + * By convention, an {@see ActionRequest} will be available as `request`: + * + * $actionRequest = $this->runtime->fusionGlobals->get('request'); + * if (!$actionRequest instanceof ActionRequest) { + * // fallback or error + * } + * + * @deprecated with Neos 9.0 can be removed with 10 + */ + public function getRequest(): ActionRequest + { + return $this->request; + } + + /** + * To migrate the use case of getting the UriBuilder please use this instead: + * + * $actionRequest = $this->runtime->fusionGlobals->get('request'); + * if (!$actionRequest instanceof ActionRequest) { + * // fallback or error + * } + * $uriBuilder = new UriBuilder(); + * $uriBuilder->setRequest($actionRequest); + * + * @deprecated with Neos 9.0 can be removed with 10 + */ + public function getUriBuilder(): UriBuilder + { + $uriBuilder = new UriBuilder(); + $uriBuilder->setRequest($this->request); + return $uriBuilder; + } + + /** + * To migrate this use case please use {@see FlashMessageService::getFlashMessageContainerForRequest()} in + * combination with fetching the active request as described here {@see getRequest} instead. + * + * @deprecated with Neos 9.0 can be removed with 10 + */ + public function getFlashMessageContainer(): FlashMessageContainer + { + return $this->flashMessageService->getFlashMessageContainerForRequest($this->request); + } + + /** + * PURELY INTERNAL with partially undefined behaviour!!! + * + * Gives access to the legacy mutable action response simulation {@see Runtime::withSimulatedLegacyControllerContext()} + * + * Initially it was possible to mutate the current response of the active MVC controller though this getter. + * + * While *HIGHLY* internal behaviour and *ONLY* to be used by Neos.Fusion.Form or Neos.Neos:Plugin + * this legacy layer is in place still allows this functionality. + * + * @deprecated with Neos 9.0 can be removed with 10 + * @internal THIS SHOULD NEVER BE CALLED ON USER-LAND + */ + public function getResponse(): ActionResponse + { + // expose action response to be possibly mutated in neos forms or fusion plugins. + // this behaviour is highly internal and deprecated! + return $this->legacyActionResponseForCurrentRendering; + } + + /** + * The method {@see ControllerContext::getArguments()} was removed without replacement. + */ + // public function getArguments(): Arguments; +} diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index 2d44aaac02e..d9755b60ef6 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -20,10 +20,7 @@ use Neos\Flow\Configuration\Exception\InvalidConfigurationException; use Neos\Flow\Mvc\ActionRequest; use Neos\Flow\Mvc\ActionResponse; -use Neos\Flow\Mvc\Controller\Arguments; -use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Flow\Mvc\Exception\StopActionException; -use Neos\Flow\Mvc\Routing\UriBuilder; use Neos\Flow\ObjectManagement\ObjectManagerInterface; use Neos\Flow\Security\Exception as SecurityException; use Neos\Fusion\Core\Cache\RuntimeContentCache; @@ -978,22 +975,7 @@ private function withSimulatedLegacyControllerContext(\Closure $renderer): Respo /** * The concept of the controller context inside Fusion has been deprecated. * - * To migrate the use case of fetching the active request, please look into {@see FusionGlobals::get()} instead. - * By convention, an {@see ActionRequest} will be available as `request`: - * - * ```php - * $actionRequest = $this->runtime->fusionGlobals->get('request'); - * if (!$actionRequest instanceof ActionRequest) { - * // fallback or error - * } - * ``` - * - * To get an {@see UriBuilder} proceed with: - * - * ```php - * $uriBuilder = new UriBuilder(); - * $uriBuilder->setRequest($actionRequest); - * ``` + * For further information and migration strategies, please look into {@see LegacyFusionControllerContext} * * WARNING: * Invoking this backwards-compatible layer is possibly unsafe, if the rendering was not started @@ -1001,8 +983,9 @@ private function withSimulatedLegacyControllerContext(\Closure $renderer): Respo * * @deprecated with Neos 9.0 * @internal + * @throws Exception if unsafe call */ - public function getControllerContext(): ControllerContext + public function getControllerContext(): LegacyFusionControllerContext { // legacy controller context layer $actionRequest = $this->fusionGlobals->get('request'); @@ -1010,16 +993,11 @@ public function getControllerContext(): ControllerContext throw new Exception(sprintf('Fusions simulated legacy controller context is only available inside `Runtime::renderResponse` and when the Fusion global "request" is an ActionRequest.'), 1706458355); } - $uriBuilder = new UriBuilder(); - $uriBuilder->setRequest($actionRequest); - - return new ControllerContext( + return new LegacyFusionControllerContext( $actionRequest, // expose action response to be possibly mutated in neos forms or fusion plugins. // this behaviour is highly internal and deprecated! - $this->legacyActionResponseForCurrentRendering, - new Arguments([]), - $uriBuilder + $this->legacyActionResponseForCurrentRendering ); } diff --git a/Neos.Neos/Classes/Fusion/ExceptionHandlers/PageHandler.php b/Neos.Neos/Classes/Fusion/ExceptionHandlers/PageHandler.php index 56ff4bed0a0..c20eca8bcda 100644 --- a/Neos.Neos/Classes/Fusion/ExceptionHandlers/PageHandler.php +++ b/Neos.Neos/Classes/Fusion/ExceptionHandlers/PageHandler.php @@ -19,6 +19,8 @@ use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\Exception as FlowException; +use Neos\Flow\Mvc\Controller\Arguments; +use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Flow\Security\Authorization\PrivilegeManagerInterface; use Neos\Flow\Utility\Environment; use Neos\FluidAdaptor\View\StandaloneView; @@ -129,7 +131,14 @@ protected function wrapHttpResponse(\Exception $exception, string $bodyContent): protected function prepareFluidView() { $fluidView = new StandaloneView(); - $fluidView->setControllerContext($this->runtime->getControllerContext()); + $fluidView->setControllerContext( + new ControllerContext( + $this->runtime->getControllerContext()->getRequest(), + $this->runtime->getControllerContext()->getResponse(), + new Arguments(), + $this->runtime->getControllerContext()->getUriBuilder() + ) + ); $fluidView->setFormat('html'); $fluidView->setTemplatePathAndFilename('resource://Neos.Neos/Private/Templates/Error/NeosBackendMessage.html'); $fluidView->setLayoutRootPath('resource://Neos.Neos/Private/Layouts/'); diff --git a/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php b/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php index e8aff26d8ea..be0cc8b83f8 100644 --- a/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php +++ b/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php @@ -119,6 +119,8 @@ public function responseHeadersDataProvider(): array */ public function evaluateSetHeaderIntoParent(string $message, array $input, array $expected): void { + $this->markTestSkipped('TODO Doesnt test any thing really, has to be rewritten as behat test.'); + $this->pluginImplementation->method('buildPluginRequest')->willReturn($this->mockActionRequest); $parentResponse = new ActionResponse(); From 64c1a418e1c46881ea9cf268499d9013fd978d9a Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 22 Feb 2024 21:02:55 +0100 Subject: [PATCH 056/214] TASK: Fix passing `tags` to plugin --- Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php b/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php index be0cc8b83f8..3dc6fba48dc 100644 --- a/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php +++ b/Neos.Neos/Tests/Unit/Fusion/PluginImplementationTest.php @@ -65,6 +65,8 @@ class PluginImplementationTest extends UnitTestCase public function setUp(): void { + $this->markTestSkipped('TODO Doesnt test any thing really, has to be rewritten as behat test.'); + $this->pluginImplementation = $this->getAccessibleMock(PluginImplementation::class, ['buildPluginRequest'], [], '', false); $this->mockHttpUri = $this->getMockBuilder(Uri::class)->disableOriginalConstructor()->getMock(); @@ -92,6 +94,11 @@ public function setUp(): void */ public function responseHeadersDataProvider(): array { + /* + * Fyi (from christian) Multiple competing headers like that are a broken use case anyways. + * Headers by definition can appear multiple times, we can't really know if we should remove the first one and when not. + * IMHO the test is misleading the result might as well (correctly) be key => [value, value] + */ return [ [ 'Plugin response key does already exist in parent with same value', @@ -119,8 +126,6 @@ public function responseHeadersDataProvider(): array */ public function evaluateSetHeaderIntoParent(string $message, array $input, array $expected): void { - $this->markTestSkipped('TODO Doesnt test any thing really, has to be rewritten as behat test.'); - $this->pluginImplementation->method('buildPluginRequest')->willReturn($this->mockActionRequest); $parentResponse = new ActionResponse(); From 35d06fb6d6ff48d399081b85299b56ddf8e6d159 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 23 Feb 2024 16:21:42 +0100 Subject: [PATCH 057/214] !!! BUGFIX: Fusions `FusionView` will return `ResponseInterface` instead of `mixed` This will make the `Neos.Fusion:Http.Message` prototype work out of the box. This was also done to return a value allowed by the view: `string|ActionResponse|ResponseInterface|StreamInterface|\Stringable` Adjusts AbstractFusionObjectTest::buildView to the runtime directly. We still use a `ViewInterface` here as this allows us to not rewrite all our tests :D We will rewrite them either way in behat some time. --- Neos.Fusion/Classes/View/FusionView.php | 28 ++---- .../AbstractFusionObjectTest.php | 88 ++++++++++++++++--- .../FusionObjects/ReservedKeysTest.php | 10 ++- .../View/Fixtures/Fusion/Root.fusion | 4 + .../Tests/Functional/View/FusionViewTest.php | 33 ++++++- 5 files changed, 123 insertions(+), 40 deletions(-) diff --git a/Neos.Fusion/Classes/View/FusionView.php b/Neos.Fusion/Classes/View/FusionView.php index 1d300f57f49..f418bc7ec9e 100644 --- a/Neos.Fusion/Classes/View/FusionView.php +++ b/Neos.Fusion/Classes/View/FusionView.php @@ -22,7 +22,7 @@ use Neos\Fusion\Core\Parser; use Neos\Fusion\Core\Runtime; use Neos\Fusion\Core\RuntimeFactory; -use Neos\Fusion\Exception\RuntimeException; +use Psr\Http\Message\ResponseInterface; /** * View for using Fusion for standard MVC controllers. @@ -162,13 +162,16 @@ public function setFusionPathPatterns(array $pathPatterns) /** * Render the view * - * @return mixed The rendered view + * @return ResponseInterface The rendered view * @api */ - public function render() + public function render(): ResponseInterface { $this->initializeFusionRuntime(); - return $this->renderFusion(); + return $this->fusionRuntime->renderResponse( + $this->getFusionPathForCurrentRequest(), + $this->variables + ); } /** @@ -298,23 +301,6 @@ protected function getFusionPathForCurrentRequest() return $this->fusionPath; } - /** - * Render the given Fusion and return the rendered page - * @return mixed - * @throws \Exception - */ - protected function renderFusion() - { - $this->fusionRuntime->pushContextArray($this->variables); - try { - $output = $this->fusionRuntime->render($this->getFusionPathForCurrentRequest()); - } catch (RuntimeException $exception) { - throw $exception->getPrevious(); - } - $this->fusionRuntime->popContext(); - return $output; - } - private function getRequestFromFusionGlobals(): ?ActionRequest { /** @var FusionGlobals $fusionGlobals */ diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php b/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php index 749af8fe021..94cfca2874d 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php +++ b/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php @@ -11,10 +11,16 @@ * source code. */ +use GuzzleHttp\Psr7\ServerRequest; use Neos\Flow\Mvc\ActionRequest; +use Neos\Flow\Mvc\Controller\ControllerContext; +use Neos\Flow\Mvc\View\ViewInterface; use Neos\Flow\Tests\FunctionalTestCase; use Neos\Fusion\Core\FusionGlobals; -use Neos\Fusion\View\FusionView; +use Neos\Fusion\Core\FusionSourceCodeCollection; +use Neos\Fusion\Core\Runtime; +use Neos\Fusion\Core\RuntimeFactory; +use Neos\Fusion\Exception\RuntimeException; use Psr\Http\Message\ServerRequestFactoryInterface; /** @@ -29,25 +35,81 @@ abstract class AbstractFusionObjectTest extends FunctionalTestCase protected $request; /** - * Helper to build a Fusion view object + * TODO THIS IS HACKY AS WE CREATE AN OWN VIEW * - * @return FusionView + * We do that as the FusionView (rightfully) does return mixed anymore. + * + * We could instead also rewrite all tests to use the Runtime instead. + * + * But that would be a lot of effort for nothing. + * + * Instead we want to refactor our tests to behat at some point. + * + * Thus the hack. + * + * @return ViewInterface */ protected function buildView() { - $view = new FusionView(); - /** @var ServerRequestFactoryInterface $httpRequestFactory */ - $httpRequestFactory = $this->objectManager->get(ServerRequestFactoryInterface::class); - $httpRequest = $httpRequestFactory->createServerRequest('GET', 'http://localhost/'); - $this->request = ActionRequest::fromHttpRequest($httpRequest); + $this->request = ActionRequest::fromHttpRequest(new ServerRequest('GET', 'http://localhost/')); + + $runtime = $this->objectManager->get(RuntimeFactory::class)->createFromSourceCode( + FusionSourceCodeCollection::fromFilePath(__DIR__ . '/Fixtures/Fusion/Root.fusion'), + FusionGlobals::fromArray(['request' => $this->request]) + ); - $view->setPackageKey('Neos.Fusion'); - $view->setOption('fusionGlobals', FusionGlobals::fromArray(['request' => $this->request])); - $view->setFusionPathPattern(__DIR__ . '/Fixtures/Fusion'); - $view->assign('fixtureDirectory', __DIR__ . '/Fixtures/'); + $runtime->pushContext('fixtureDirectory', __DIR__ . '/Fixtures/'); - return $view; + // todo rewrite everything as behat test :D + return new class($runtime) implements ViewInterface { + private string $fusionPath; + public function __construct( + private readonly Runtime $runtime + ) { + } + public function setFusionPath(string $fusionPath) + { + $this->fusionPath = $fusionPath; + } + public function assign($key, $value) + { + $this->runtime->pushContext($key, $value); + } + public function setOption($key, $value) + { + match ($key) { + 'enableContentCache' => $this->runtime->setEnableContentCache($value), + 'debugMode' => $this->runtime->setDebugMode($value) + }; + } + public function assignMultiple(array $values) + { + foreach ($values as $key => $value) { + $this->runtime->pushContext($key, $value); + } + } + public function render() + { + try { + return $this->runtime->render($this->fusionPath); + } catch (RuntimeException $e) { + throw $e->getWrappedException(); + } + } + public static function createWithOptions(array $options) + { + throw new \BadMethodCallException(); + } + public function setControllerContext(ControllerContext $controllerContext) + { + throw new \BadMethodCallException(); + } + public function canRender(ControllerContext $controllerContext) + { + throw new \BadMethodCallException(); + } + }; } /** diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/ReservedKeysTest.php b/Neos.Fusion/Tests/Functional/FusionObjects/ReservedKeysTest.php index d0789617ca2..cbf2d3b72e1 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/ReservedKeysTest.php +++ b/Neos.Fusion/Tests/Functional/FusionObjects/ReservedKeysTest.php @@ -11,6 +11,9 @@ * source code. */ +use Neos\Fusion\Core\FusionGlobals; +use Neos\Fusion\Core\FusionSourceCodeCollection; +use Neos\Fusion\Core\RuntimeFactory; use Neos\Fusion\Exception; /** @@ -25,9 +28,10 @@ class ReservedKeysTest extends AbstractFusionObjectTest public function usingReservedKeysThrowsException() { $this->expectException(Exception::class); - $view = $this->buildView(); - $view->setFusionPathPattern(__DIR__ . '/Fixtures/ReservedKeysFusion'); - $view->render(); + $this->objectManager->get(RuntimeFactory::class)->createFromSourceCode( + FusionSourceCodeCollection::fromFilePath(__DIR__ . '/Fixtures/ReservedKeysFusion/ReservedKeys.fusion'), + FusionGlobals::empty() + ); } /** diff --git a/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/Root.fusion b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/Root.fusion index b190f6bd5a0..fbcf0fc40a3 100644 --- a/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/Root.fusion +++ b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/Root.fusion @@ -1 +1,5 @@ include: ./**/*.fusion +include: 'resource://Neos.Fusion/Private/Fusion/Prototypes/Join.fusion' +include: 'resource://Neos.Fusion/Private/Fusion/Prototypes/DataStructure.fusion' +include: 'resource://Neos.Fusion/Private/Fusion/Prototypes/Http.Message.fusion' +include: 'resource://Neos.Fusion/Private/Fusion/Prototypes/Http.ResponseHead.fusion' diff --git a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php index 1969b7ada0d..7ccace20963 100644 --- a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php +++ b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php @@ -15,6 +15,7 @@ use Neos\Flow\Tests\FunctionalTestCase; use Neos\Fusion\Core\FusionGlobals; use Neos\Fusion\View\FusionView; +use Psr\Http\Message\ResponseInterface; /** * Testcase for the Fusion View @@ -28,7 +29,7 @@ class FusionViewTest extends FunctionalTestCase public function fusionViewIsUsedForRendering() { $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); - self::assertEquals('X', $view->render()); + self::assertEquals('X', $view->render()->getBody()->getContents()); } /** @@ -38,7 +39,7 @@ public function fusionViewUsesGivenPathIfSet() { $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); $view->setFusionPath('foo/bar'); - self::assertEquals('Xfoobar', $view->render()); + self::assertEquals('Xfoobar', $view->render()->getBody()->getContents()); } /** @@ -48,7 +49,33 @@ public function fusionViewOutputsVariable() { $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); $view->assign('test', 'Hallo Welt'); - self::assertEquals('XHallo Welt', $view->render()); + self::assertEquals('XHallo Welt', $view->render()->getBody()->getContents()); + } + + /** + * @test + */ + public function fusionVieReturnsHttpResponse() + { + $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); + $view->assign('test', 'Hallo Welt'); + $response = $view->render(); + self::assertInstanceOf(ResponseInterface::class, $response); + self::assertEquals('XHallo Welt', $view->render()->getBody()->getContents()); + } + + /** + * @test + */ + public function fusionViewReturnsHttpResponseFromHttpMessagePrototype() + { + $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); + $view->setFusionPath('response'); + $response = $view->render(); + self::assertInstanceOf(ResponseInterface::class, $response); + self::assertSame('{"some":"json"}', $response->getBody()->getContents()); + self::assertSame(404, $response->getStatusCode()); + self::assertSame('application/json', $response->getHeaderLine('Content-Type')); } /** From a24805e5672e6d9b567dfe4d5f95b09fa09c5bf3 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 23 Feb 2024 16:22:50 +0100 Subject: [PATCH 058/214] TASK: `FusionExceptionView` handles psr response correctly --- .../Classes/View/FusionExceptionView.php | 37 ++++--------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/Neos.Neos/Classes/View/FusionExceptionView.php b/Neos.Neos/Classes/View/FusionExceptionView.php index 4feb70ea7c5..c70f22ee50f 100644 --- a/Neos.Neos/Classes/View/FusionExceptionView.php +++ b/Neos.Neos/Classes/View/FusionExceptionView.php @@ -22,9 +22,6 @@ use Neos\Flow\Core\Bootstrap; use Neos\Flow\Http\RequestHandler as HttpRequestHandler; use Neos\Flow\Mvc\ActionRequest; -use Neos\Flow\Mvc\ActionResponse; -use Neos\Flow\Mvc\Controller\Arguments; -use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Flow\Mvc\Routing\UriBuilder; use Neos\Flow\Mvc\View\AbstractView; use Neos\Flow\ObjectManagement\ObjectManagerInterface; @@ -90,14 +87,7 @@ class FusionExceptionView extends AbstractView #[Flow\Inject] protected DomainRepository $domainRepository; - /** - * @return mixed - * @throws \Neos\Flow\I18n\Exception\InvalidLocaleIdentifierException - * @throws \Neos\Fusion\Exception - * @throws \Neos\Neos\Domain\Exception - * @throws \Neos\Flow\Security\Exception - */ - public function render() + public function render(): string { $requestHandler = $this->bootstrap->getActiveRequestHandler(); @@ -142,18 +132,12 @@ public function render() $request->setFormat('html'); $uriBuilder = new UriBuilder(); $uriBuilder->setRequest($request); - $controllerContext = new ControllerContext( - $request, - new ActionResponse(), - new Arguments([]), - $uriBuilder - ); /** @var SecurityContext $securityContext */ $securityContext = $this->objectManager->get(SecurityContext::class); $securityContext->setRequest($request); - $fusionRuntime = $this->getFusionRuntime($currentSiteNode, $controllerContext); + $fusionRuntime = $this->getFusionRuntime($currentSiteNode, $request); $this->setFallbackRuleFromDimension($dimensionSpacePoint); @@ -175,16 +159,9 @@ public function render() return $httpResponse->getBody()->getContents(); } - /** - * @param Node $currentSiteNode - * @param ControllerContext $controllerContext - * @return FusionRuntime - * @throws \Neos\Fusion\Exception - * @throws \Neos\Neos\Domain\Exception - */ protected function getFusionRuntime( Node $currentSiteNode, - ControllerContext $controllerContext + ActionRequest $actionRequest ): FusionRuntime { if ($this->fusionRuntime === null) { $site = $this->siteRepository->findSiteBySiteNode($currentSiteNode); @@ -192,7 +169,7 @@ protected function getFusionRuntime( $fusionConfiguration = $this->fusionService->createFusionConfigurationFromSite($site); $fusionGlobals = FusionGlobals::fromArray([ - 'request' => $controllerContext->getRequest(), + 'request' => $actionRequest, 'renderingMode' => RenderingMode::createFrontend() ]); $this->fusionRuntime = $this->runtimeFactory->createFromConfiguration( @@ -207,17 +184,19 @@ protected function getFusionRuntime( return $this->fusionRuntime; } - private function renderErrorWelcomeScreen(): mixed + private function renderErrorWelcomeScreen(): string { // in case no neos site being there or no site node we cannot continue with the fusion exception view, // as we wouldn't know the site and cannot get the site's root.fusion // instead we render the welcome screen directly + /** @var \Neos\Fusion\View\FusionView $view */ $view = \Neos\Fusion\View\FusionView::createWithOptions([ 'fusionPath' => 'Neos/Fusion/NotFoundExceptions', 'fusionPathPatterns' => ['resource://Neos.Neos/Private/Fusion/Error/Root.fusion'], 'enableContentCache' => false, ]); $view->assignMultiple($this->variables); - return $view->render(); + $output = $view->render(); + return $output->getBody()->getContents(); } } From 9450f749e17290dfa1416fc65e9c50631f5c9972 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 23 Feb 2024 19:39:53 +0100 Subject: [PATCH 059/214] TASK: `Runtime::renderResponse` will try to jsonSerialize result if not a string After a discussion with Christian we found it to be a more workable behaviour than throwing an exception. --- Neos.Fusion/Classes/Core/Runtime.php | 25 ++- .../Fixtures/Fusion/JsonSerializesPath.fusion | 5 + .../Tests/Functional/View/FusionViewTest.php | 14 ++ Neos.Fusion/Tests/Unit/Core/RuntimeTest.php | 171 ++++++++++++++++++ 4 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/JsonSerializesPath.fusion diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index d9755b60ef6..4fae3e65256 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -13,7 +13,6 @@ use GuzzleHttp\Psr7\Message; use GuzzleHttp\Psr7\Response; -use GuzzleHttp\Psr7\Utils; use Psr\Http\Message\ResponseInterface; use Neos\Eel\Utility as EelUtility; use Neos\Flow\Annotations as Flow; @@ -265,6 +264,9 @@ public function getLastEvaluationStatus() return $this->lastEvaluationStatus; } + /** + * todo + */ public function renderResponse(string $fusionPath, array $contextArray): ResponseInterface { /** Unlike pushContextArray, we don't allow to overrule fusion globals {@see self::pushContext} */ @@ -293,14 +295,21 @@ public function renderResponse(string $fusionPath, array $contextArray): Respons return Message::parseResponse($output); } - $stream = match (true) { - is_string($output), - $output instanceof \Stringable => Utils::streamFor((string)$output), - $output === null, $output === false => Utils::streamFor(''), - default => throw new \RuntimeException(sprintf('Cannot render %s into http response body.', get_debug_type($output)), 1706454898) - }; + if (is_string($output) || $output instanceof \Stringable || $output === null) { + return new Response(body: $output); + } + + if (is_array($output) || $output instanceof \JsonSerializable || $output instanceof \stdClass || is_bool($output)) { + try { + $jsonSerialized = json_encode($output, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new \RuntimeException(sprintf('Cannot render %s into http response body.', get_debug_type($output)), 1708713158, $e); + } + $jsonResponse = new Response(body: $jsonSerialized); + return $jsonResponse->withHeader('Content-Type', 'application/json'); + } - return new Response(body: $stream); + throw new \RuntimeException(sprintf('Cannot render %s into http response body.', get_debug_type($output)), 1706454898); }); } diff --git a/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/JsonSerializesPath.fusion b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/JsonSerializesPath.fusion new file mode 100644 index 00000000000..ca59920f7d3 --- /dev/null +++ b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/JsonSerializesPath.fusion @@ -0,0 +1,5 @@ + +jsonSerializeable = Neos.Fusion:DataStructure { + my = 'array' + with = 'values' +} diff --git a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php index 7ccace20963..6dd106eead3 100644 --- a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php +++ b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php @@ -78,6 +78,20 @@ public function fusionViewReturnsHttpResponseFromHttpMessagePrototype() self::assertSame('application/json', $response->getHeaderLine('Content-Type')); } + /** + * @test + */ + public function fusionViewJsonSerializesOutputIfNotString() + { + $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); + $view->setFusionPath('jsonSerializeable'); + $response = $view->render(); + self::assertInstanceOf(ResponseInterface::class, $response); + self::assertSame('{"my":"array","with":"values"}', $response->getBody()->getContents()); + self::assertSame(200, $response->getStatusCode()); + self::assertSame('application/json', $response->getHeaderLine('Content-Type')); + } + /** * Prepare a FusionView for testing that Mocks a request with the given controller and action names. * diff --git a/Neos.Fusion/Tests/Unit/Core/RuntimeTest.php b/Neos.Fusion/Tests/Unit/Core/RuntimeTest.php index bc9920a0140..d0c9dc88997 100644 --- a/Neos.Fusion/Tests/Unit/Core/RuntimeTest.php +++ b/Neos.Fusion/Tests/Unit/Core/RuntimeTest.php @@ -11,6 +11,7 @@ * source code. */ +use GuzzleHttp\Psr7\Message; use Neos\Eel\EelEvaluatorInterface; use Neos\Eel\ProtectedContext; use Neos\Flow\Exception; @@ -22,6 +23,7 @@ use Neos\Fusion\Core\Runtime; use Neos\Fusion\Exception\RuntimeException; use Neos\Fusion\FusionObjects\ValueImplementation; +use Psr\Http\Message\ResponseInterface; class RuntimeTest extends UnitTestCase { @@ -207,6 +209,18 @@ public function pushContextIsNotAllowedToOverrideFusionGlobals() $runtime->pushContext('request', 'anything'); } + /** + * @test + */ + public function renderResponseIsNotAllowedToOverrideFusionGlobals() + { + $this->expectException(\Neos\Fusion\Exception::class); + $this->expectExceptionMessage('Overriding Fusion global variable "request" via @context is not allowed.'); + $runtime = new Runtime(FusionConfiguration::fromArray([]), FusionGlobals::fromArray(['request' => 'fixed'])); + + $runtime->renderResponse('foo', ['request' =>'anything']); + } + /** * Legacy compatible layer to possibly override fusion globals like "request". * This functionality is only allowed for internal packages. @@ -222,4 +236,161 @@ public function pushContextArrayIsAllowedToOverrideFusionGlobals() $runtime->pushContextArray(['bing' => 'beer', 'request' => 'anything']); self::assertTrue(true); } + + public static function renderResponseExamples(): iterable + { + yield 'simple string' => [ + 'rawValue' => 'my string', + 'response' => <<<'TEXT' + HTTP/1.1 200 OK + + my string + TEXT + ]; + + yield 'string cast object (\Stringable)' => [ + 'rawValue' => new class implements \Stringable, \JsonSerializable { + public function __toString() + { + return 'my string karsten'; + } + // __toString is preferred + public function jsonSerialize(): mixed + { + return ['my string']; + } + }, + 'response' => <<<'TEXT' + HTTP/1.1 200 OK + + my string karsten + TEXT + ]; + + yield 'empty string' => [ + 'rawValue' => '', + 'response' => <<<'TEXT' + HTTP/1.1 200 OK + + + TEXT + ]; + + yield 'null value' => [ + 'rawValue' => null, + 'response' => <<<'TEXT' + HTTP/1.1 200 OK + + + TEXT + ]; + + yield 'stringified http response string is upcasted' => [ + 'rawValue' => <<<'TEXT' + HTTP/1.1 418 OK + Content-Type: text/html + X-MyCustomHeader: marc + + + + Hello World + TEXT, + 'response' => <<<'TEXT' + HTTP/1.1 418 OK + Content-Type: text/html + X-MyCustomHeader: marc + + + + Hello World + TEXT + ]; + + yield 'json serialize array' => [ + 'rawValue' => ['my' => 'array', 'with' => 'values'], + 'response' => <<<'TEXT' + HTTP/1.1 200 OK + Content-Type: application/json + + {"my":"array","with":"values"} + TEXT + ]; + + yield 'json serialize \stdClass' => [ + 'rawValue' => (object)[], + 'response' => <<<'TEXT' + HTTP/1.1 200 OK + Content-Type: application/json + + {} + TEXT + ]; + + yield 'json serialize object (\JsonSerializable)' => [ + 'rawValue' => new class implements \JsonSerializable { + public function jsonSerialize(): mixed + { + return ['my' => 'object', 'with' => 'values']; + } + }, + 'response' => <<<'TEXT' + HTTP/1.1 200 OK + Content-Type: application/json + + {"my":"object","with":"values"} + TEXT + ]; + + yield 'json serialize boolean' => [ + 'rawValue' => false, + 'response' => <<<'TEXT' + HTTP/1.1 200 OK + Content-Type: application/json + + false + TEXT + ]; + } + + /** + * @test + * @dataProvider renderResponseExamples + */ + public function renderResponse(mixed $rawValue, string $expectedHttpResponseString) + { + $runtime = $this->getMockBuilder(Runtime::class) + ->setConstructorArgs([FusionConfiguration::fromArray([]), FusionGlobals::empty()]) + ->onlyMethods(['render']) + ->getMock(); + + $runtime->expects(self::once())->method('render')->willReturn( + is_string($rawValue) ? str_replace("\n", "\r\n", $rawValue) : $rawValue + ); + + $response = $runtime->renderResponse('/path', []); + + self::assertInstanceOf(ResponseInterface::class, $response); + self::assertSame(str_replace("\n", "\r\n", $expectedHttpResponseString), Message::toString($response)); + } + + /** + * @test + */ + public function renderResponseThrowsIfNotStringableOrJsonSerializeable() + { + $illegalValue = new class { + }; + $this->expectExceptionMessage('Cannot render class@anonymous into http response body.'); + + $runtime = $this->getMockBuilder(Runtime::class) + ->setConstructorArgs([FusionConfiguration::fromArray([]), FusionGlobals::empty()]) + ->onlyMethods(['render']) + ->getMock(); + + $runtime->expects(self::once())->method('render')->willReturn( + $illegalValue + ); + + $runtime->renderResponse('/path', []); + } } From 2e79a6ddc769d6f30757a187e4939953b13ad805 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 25 Feb 2024 11:11:07 +0100 Subject: [PATCH 060/214] TASK: Improve `fusionGlobals` handling in the Fusion `FusionView` Initially introduced the option expected an actual instance of `FusionGlobals`. This is a flawed design as only primitives can be set for example via `Views.yaml` Additionally, the `FusionView` is forward compatible for this change https://github.com/neos/flow-development-collection/pull/3286 The `fusionGlobals` must not be used for setting the request but rather ``` $view->assign('request"', $this->request) ``` --- Neos.Fusion/Classes/View/FusionView.php | 69 +++++++++---------- .../Tests/Functional/View/FusionViewTest.php | 3 +- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/Neos.Fusion/Classes/View/FusionView.php b/Neos.Fusion/Classes/View/FusionView.php index f418bc7ec9e..1bb5439d0fc 100644 --- a/Neos.Fusion/Classes/View/FusionView.php +++ b/Neos.Fusion/Classes/View/FusionView.php @@ -44,7 +44,7 @@ class FusionView extends AbstractView protected $supportedOptions = [ 'fusionPathPatterns' => [['resource://@package/Private/Fusion'], 'Fusion files that will be loaded if directories are given the Root.fusion is used.', 'array'], 'fusionPath' => [null, 'The Fusion path which should be rendered; derived from the controller and action names or set by the user.', 'string'], - 'fusionGlobals' => [null, 'Additional global variables; merged together with the "request". Must only be specified at creation.', FusionGlobals::class], + 'fusionGlobals' => [null, 'Additional Fusion global variables. The request must be assigned using `assign`. For regular variables please use `assign` as well.', 'array'], 'packageKey' => [null, 'The package key where the Fusion should be loaded from. If not given, is automatically derived from the current request.', 'string'], 'debugMode' => [false, 'Flag to enable debug mode of the Fusion runtime explicitly (overriding the global setting).', 'boolean'], 'enableContentCache' => [false, 'Flag to enable content caching inside Fusion (overriding the global setting).', 'boolean'] @@ -82,6 +82,12 @@ class FusionView extends AbstractView */ protected $fusionRuntime = null; + /** + * Via {@see assign} request using the "request" key, + * will be available also as Fusion global in the runtime. + */ + protected ?ActionRequest $assignedActionRequest = null; + /** * Reset runtime cache if an option is changed * @@ -91,30 +97,37 @@ class FusionView extends AbstractView */ public function setOption($optionName, $value) { + // todo do we want to allow to set the `fusionPathPatterns` after the first render? $this->fusionPath = null; parent::setOption($optionName, $value); } + public function assign($key, $value) + { + if ($key === 'request') { + // the request cannot be used as "normal" fusion variable and must be treated as FusionGlobal + // to for example not cache it accidentally + // additionally we need it for special request based handling in the view + $this->assignedActionRequest = $value; + return $this; + } + return parent::assign($key, $value); + } + /** - * Legacy layer to make the `request` available in Fusion. + * Legacy layer to set the request for this view if not set already. * - * Please use {@see setOption} and {@see FusionGlobals} instead: + * Please use {@see assign} with "request" instead * - * $view->setOption('fusionGlobals', FusionGlobals::fromArray(['request' => $this->request])) + * $view->assign('request"', $this->request) * * @deprecated with Neos 9 */ public function setControllerContext(ControllerContext $controllerContext) { - /** @var FusionGlobals $fusionGlobals */ - $fusionGlobals = $this->options['fusionGlobals'] ?? FusionGlobals::empty(); - if ($fusionGlobals->has('request')) { - // no need for legacy layer as the request was already set. - return; + if (!$this->assignedActionRequest) { + $this->assignedActionRequest = $controllerContext->getRequest(); } - $this->setOption('fusionGlobals', $fusionGlobals->merge(FusionGlobals::fromArray( - ['request' => $controllerContext->getRequest()] - ))); } /** @@ -185,14 +198,15 @@ public function initializeFusionRuntime() { if ($this->fusionRuntime === null) { $this->loadFusion(); - - $fusionGlobals = $this->options['fusionGlobals'] ?? FusionGlobals::empty(); - if (!$fusionGlobals instanceof FusionGlobals) { - throw new \InvalidArgumentException('View option "fusionGlobals" must be of type FusionGlobals', 1694252923947); + $additionalGlobals = FusionGlobals::fromArray($this->options['fusionGlobals'] ?? []); + if ($additionalGlobals->has('request')) { + throw new \RuntimeException(sprintf('The request cannot be set using the additional fusion globals. Please use $view->assign("request", ...) instead.'), 1708854895); } $this->fusionRuntime = $this->runtimeFactory->createFromConfiguration( $this->parsedFusion, - $fusionGlobals + $this->assignedActionRequest + ? $additionalGlobals->merge(FusionGlobals::fromArray(['request' => $this->assignedActionRequest])) + : $additionalGlobals ); } if (isset($this->options['debugMode'])) { @@ -265,11 +279,10 @@ protected function getPackageKey() if ($packageKey !== null) { return $packageKey; } else { - $request = $this->getRequestFromFusionGlobals(); - if (!$request) { + if (!$this->assignedActionRequest) { throw new \RuntimeException(sprintf('To resolve the @package in all fusionPathPatterns, either packageKey has to be specified, or the current request be available.'), 1708267874); } - return $request->getControllerPackageKey(); + return $this->assignedActionRequest->getControllerPackageKey(); } } @@ -285,7 +298,7 @@ protected function getFusionPathForCurrentRequest() if ($fusionPath !== null) { $this->fusionPath = $fusionPath; } else { - $request = $this->getRequestFromFusionGlobals(); + $request = $this->assignedActionRequest; if (!$request) { throw new \RuntimeException(sprintf('The option `fusionPath` was not set. Could not fallback to the current request.'), 1708267857); } @@ -300,18 +313,4 @@ protected function getFusionPathForCurrentRequest() } return $this->fusionPath; } - - private function getRequestFromFusionGlobals(): ?ActionRequest - { - /** @var FusionGlobals $fusionGlobals */ - $fusionGlobals = $this->options['fusionGlobals'] ?? FusionGlobals::empty(); - if (!$fusionGlobals instanceof FusionGlobals) { - throw new \InvalidArgumentException('View option "fusionGlobals" must be of type FusionGlobals', 1694252923947); - } - $actionRequest = $fusionGlobals->get('request'); - if (!$actionRequest instanceof ActionRequest) { - return null; - } - return $actionRequest; - } } diff --git a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php index 6dd106eead3..4d9f17426fc 100644 --- a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php +++ b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php @@ -13,7 +13,6 @@ use Neos\Flow\Mvc\ActionRequest; use Neos\Flow\Tests\FunctionalTestCase; -use Neos\Fusion\Core\FusionGlobals; use Neos\Fusion\View\FusionView; use Psr\Http\Message\ResponseInterface; @@ -106,7 +105,7 @@ protected function buildView($controllerObjectName, $controllerActionName) $request->expects(self::any())->method('getControllerActionName')->will(self::returnValue($controllerActionName)); $view = new FusionView(); - $view->setOption('fusionGlobals', FusionGlobals::fromArray(['request' => $request])); + $view->assign('request', $request); $view->setFusionPathPattern(__DIR__ . '/Fixtures/Fusion'); return $view; From 4184042dd8af8804a6c87fa61e07f24a0860af7c Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 25 Feb 2024 11:46:56 +0100 Subject: [PATCH 061/214] TASK: Runtime remove magic fusion jsonSerialize again This magic behaviour is not expected and will rather lead to bugs instead of help the integrator to use fusion. The action controllers `renderView` would previously just silently ignore this case and render a blank page. Instead, to force to write correct functioning entry points, we throw an exception like: > Fusion entry path "root" is expected to render a compatible http response body: string|\Stringable|null. Got array instead. --- .../IllegalEntryFusionPathValueException.php | 9 ++ Neos.Fusion/Classes/Core/Runtime.php | 41 +++---- .../Fusion/IllegalEntryPointValue.fusion | 4 + .../Fixtures/Fusion/JsonSerializesPath.fusion | 5 - .../Tests/Functional/View/FusionViewTest.php | 14 +-- Neos.Fusion/Tests/Unit/Core/RuntimeTest.php | 102 +++++++----------- 6 files changed, 78 insertions(+), 97 deletions(-) create mode 100644 Neos.Fusion/Classes/Core/IllegalEntryFusionPathValueException.php create mode 100644 Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/IllegalEntryPointValue.fusion delete mode 100644 Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/JsonSerializesPath.fusion diff --git a/Neos.Fusion/Classes/Core/IllegalEntryFusionPathValueException.php b/Neos.Fusion/Classes/Core/IllegalEntryFusionPathValueException.php new file mode 100644 index 00000000000..f5f57b8fb1f --- /dev/null +++ b/Neos.Fusion/Classes/Core/IllegalEntryFusionPathValueException.php @@ -0,0 +1,9 @@ + $_) { + // Like in pushContext, we don't allow to overrule fusion globals + foreach ($contextVariables as $key => $_) { if ($this->fusionGlobals->has($key)) { throw new Exception(sprintf('Overriding Fusion global variable "%s" via @context is not allowed.', $key), 1706452063); } } - $this->pushContextArray($contextArray); + // replace any previously assigned values + $this->pushContextArray($contextVariables); - return $this->withSimulatedLegacyControllerContext(function () use ($fusionPath) { + return $this->withSimulatedLegacyControllerContext(function () use ($entryFusionPath) { try { - $output = $this->render($fusionPath); + $output = $this->render($entryFusionPath); } catch (RuntimeException $exception) { throw $exception->getWrappedException(); } finally { $this->popContext(); } - /** - * parse potential raw http response possibly rendered via "Neos.Fusion:Http.Message" - * {@see \Neos\Fusion\FusionObjects\HttpResponseImplementation} - */ + // Parse potential raw http response possibly rendered via "Neos.Fusion:Http.Message" + /** {@see \Neos\Fusion\FusionObjects\HttpResponseImplementation} */ $outputStringHasHttpPreamble = is_string($output) && str_starts_with($output, 'HTTP/'); if ($outputStringHasHttpPreamble) { return Message::parseResponse($output); } - if (is_string($output) || $output instanceof \Stringable || $output === null) { - return new Response(body: $output); - } - - if (is_array($output) || $output instanceof \JsonSerializable || $output instanceof \stdClass || is_bool($output)) { - try { - $jsonSerialized = json_encode($output, JSON_THROW_ON_ERROR); - } catch (\JsonException $e) { - throw new \RuntimeException(sprintf('Cannot render %s into http response body.', get_debug_type($output)), 1708713158, $e); - } - $jsonResponse = new Response(body: $jsonSerialized); - return $jsonResponse->withHeader('Content-Type', 'application/json'); + if (!is_string($output) && !$output instanceof \Stringable && $output !== null) { + throw new IllegalEntryFusionPathValueException(sprintf('Fusion entry path "%s" is expected to render a compatible http response body: string|\Stringable|null. Got %s instead.', $entryFusionPath, get_debug_type($output)), 1706454898); } - throw new \RuntimeException(sprintf('Cannot render %s into http response body.', get_debug_type($output)), 1706454898); + return new Response(body: $output); }); } diff --git a/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/IllegalEntryPointValue.fusion b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/IllegalEntryPointValue.fusion new file mode 100644 index 00000000000..03a1c95fa61 --- /dev/null +++ b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/IllegalEntryPointValue.fusion @@ -0,0 +1,4 @@ + +illegalEntryPointValue = Neos.Fusion:DataStructure { + my = 'array' +} diff --git a/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/JsonSerializesPath.fusion b/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/JsonSerializesPath.fusion deleted file mode 100644 index ca59920f7d3..00000000000 --- a/Neos.Fusion/Tests/Functional/View/Fixtures/Fusion/JsonSerializesPath.fusion +++ /dev/null @@ -1,5 +0,0 @@ - -jsonSerializeable = Neos.Fusion:DataStructure { - my = 'array' - with = 'values' -} diff --git a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php index 4d9f17426fc..f59dad2cf69 100644 --- a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php +++ b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php @@ -13,6 +13,7 @@ use Neos\Flow\Mvc\ActionRequest; use Neos\Flow\Tests\FunctionalTestCase; +use Neos\Fusion\Core\IllegalEntryFusionPathValueException; use Neos\Fusion\View\FusionView; use Psr\Http\Message\ResponseInterface; @@ -80,15 +81,14 @@ public function fusionViewReturnsHttpResponseFromHttpMessagePrototype() /** * @test */ - public function fusionViewJsonSerializesOutputIfNotString() + public function fusionViewCannotRenderNonStringableValue() { + $this->expectException(IllegalEntryFusionPathValueException::class); + $this->expectExceptionMessage('Fusion entry path "illegalEntryPointValue" is expected to render a compatible http response body: string|\Stringable|null. Got array instead.'); + $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); - $view->setFusionPath('jsonSerializeable'); - $response = $view->render(); - self::assertInstanceOf(ResponseInterface::class, $response); - self::assertSame('{"my":"array","with":"values"}', $response->getBody()->getContents()); - self::assertSame(200, $response->getStatusCode()); - self::assertSame('application/json', $response->getHeaderLine('Content-Type')); + $view->setFusionPath('illegalEntryPointValue'); + $view->render(); } /** diff --git a/Neos.Fusion/Tests/Unit/Core/RuntimeTest.php b/Neos.Fusion/Tests/Unit/Core/RuntimeTest.php index d0c9dc88997..4716d1f6cbd 100644 --- a/Neos.Fusion/Tests/Unit/Core/RuntimeTest.php +++ b/Neos.Fusion/Tests/Unit/Core/RuntimeTest.php @@ -20,6 +20,7 @@ use Neos\Fusion\Core\ExceptionHandlers\ThrowingHandler; use Neos\Fusion\Core\FusionConfiguration; use Neos\Fusion\Core\FusionGlobals; +use Neos\Fusion\Core\IllegalEntryFusionPathValueException; use Neos\Fusion\Core\Runtime; use Neos\Fusion\Exception\RuntimeException; use Neos\Fusion\FusionObjects\ValueImplementation; @@ -38,9 +39,9 @@ public function renderHandlesExceptionDuringRendering() $runtimeException = new RuntimeException('I am a parent exception', 123, new Exception('I am a previous exception'), 'root'); $runtime = $this->getMockBuilder(Runtime::class)->onlyMethods(['evaluate', 'handleRenderingException'])->disableOriginalConstructor()->getMock(); $runtime->expects(self::any())->method('evaluate')->will(self::throwException($runtimeException)); - $runtime->expects(self::once())->method('handleRenderingException')->with('/foo/bar', $runtimeException)->will(self::returnValue('Exception Message')); + $runtime->expects(self::once())->method('handleRenderingException')->with('foo/bar', $runtimeException)->will(self::returnValue('Exception Message')); - $output = $runtime->render('/foo/bar'); + $output = $runtime->render('foo/bar'); self::assertEquals('Exception Message', $output); } @@ -65,7 +66,7 @@ public function handleRenderingExceptionThrowsException() $objectManager->expects(self::once())->method('isRegistered')->with($exceptionHandlerSetting)->will(self::returnValue(true)); $objectManager->expects(self::once())->method('get')->with($exceptionHandlerSetting)->will(self::returnValue(new ThrowingHandler())); - $runtime->handleRenderingException('/foo/bar', $runtimeException); + $runtime->handleRenderingException('foo/bar', $runtimeException); } /** @@ -121,7 +122,7 @@ public function renderRethrowsSecurityExceptions() $runtime = $this->getMockBuilder(Runtime::class)->onlyMethods(['evaluate', 'handleRenderingException'])->disableOriginalConstructor()->getMock(); $runtime->expects(self::any())->method('evaluate')->will(self::throwException($securityException)); - $runtime->render('/foo/bar'); + $runtime->render('foo/bar'); } /** @@ -249,16 +250,11 @@ public static function renderResponseExamples(): iterable ]; yield 'string cast object (\Stringable)' => [ - 'rawValue' => new class implements \Stringable, \JsonSerializable { + 'rawValue' => new class implements \Stringable { public function __toString() { return 'my string karsten'; } - // __toString is preferred - public function jsonSerialize(): mixed - { - return ['my string']; - } }, 'response' => <<<'TEXT' HTTP/1.1 200 OK @@ -305,51 +301,6 @@ public function jsonSerialize(): mixed Hello World TEXT ]; - - yield 'json serialize array' => [ - 'rawValue' => ['my' => 'array', 'with' => 'values'], - 'response' => <<<'TEXT' - HTTP/1.1 200 OK - Content-Type: application/json - - {"my":"array","with":"values"} - TEXT - ]; - - yield 'json serialize \stdClass' => [ - 'rawValue' => (object)[], - 'response' => <<<'TEXT' - HTTP/1.1 200 OK - Content-Type: application/json - - {} - TEXT - ]; - - yield 'json serialize object (\JsonSerializable)' => [ - 'rawValue' => new class implements \JsonSerializable { - public function jsonSerialize(): mixed - { - return ['my' => 'object', 'with' => 'values']; - } - }, - 'response' => <<<'TEXT' - HTTP/1.1 200 OK - Content-Type: application/json - - {"my":"object","with":"values"} - TEXT - ]; - - yield 'json serialize boolean' => [ - 'rawValue' => false, - 'response' => <<<'TEXT' - HTTP/1.1 200 OK - Content-Type: application/json - - false - TEXT - ]; } /** @@ -367,20 +318,49 @@ public function renderResponse(mixed $rawValue, string $expectedHttpResponseStri is_string($rawValue) ? str_replace("\n", "\r\n", $rawValue) : $rawValue ); - $response = $runtime->renderResponse('/path', []); + $response = $runtime->renderResponse('path', []); self::assertInstanceOf(ResponseInterface::class, $response); self::assertSame(str_replace("\n", "\r\n", $expectedHttpResponseString), Message::toString($response)); } + public static function renderResponseIllegalValueExamples(): iterable + { + yield 'array' => [ + 'rawValue' => ['my' => 'array', 'with' => 'values'] + ]; + + yield '\stdClass' => [ + 'rawValue' => (object)[] + ]; + + yield '\JsonSerializable' => [ + 'rawValue' => new class implements \JsonSerializable { + public function jsonSerialize(): mixed + { + return 123; + } + } + ]; + + yield 'any class' => [ + 'rawValue' => new class { + } + ]; + + yield 'boolean' => [ + 'rawValue' => false + ]; + } + /** + * @dataProvider renderResponseIllegalValueExamples * @test */ - public function renderResponseThrowsIfNotStringableOrJsonSerializeable() + public function renderResponseThrowsIfNotStringable(mixed $illegalValue) { - $illegalValue = new class { - }; - $this->expectExceptionMessage('Cannot render class@anonymous into http response body.'); + $this->expectException(IllegalEntryFusionPathValueException::class); + $this->expectExceptionMessage(sprintf('Fusion entry path "path" is expected to render a compatible http response body: string|\Stringable|null. Got %s instead.', get_debug_type($illegalValue))); $runtime = $this->getMockBuilder(Runtime::class) ->setConstructorArgs([FusionConfiguration::fromArray([]), FusionGlobals::empty()]) @@ -391,6 +371,6 @@ public function renderResponseThrowsIfNotStringableOrJsonSerializeable() $illegalValue ); - $runtime->renderResponse('/path', []); + $runtime->renderResponse('path', []); } } From 3f4089ae8ab01cb0d4c5dec44bb990f19fa787bb Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 25 Feb 2024 12:00:52 +0100 Subject: [PATCH 062/214] TASK: Refine fusion inline docs --- .../Core/Cache/FusionContextSerializer.php | 2 +- Neos.Fusion/Classes/Core/FusionGlobals.php | 33 ++++++++++++----- Neos.Fusion/Classes/Core/RuntimeFactory.php | 5 +-- .../Cache/NeosFusionContextSerializer.php | 16 ++++---- Neos.Neos/Classes/View/FusionView.php | 37 +++++++++++++++++-- 5 files changed, 68 insertions(+), 25 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Cache/FusionContextSerializer.php b/Neos.Fusion/Classes/Core/Cache/FusionContextSerializer.php index 57c06ff3006..60040ff33f0 100644 --- a/Neos.Fusion/Classes/Core/Cache/FusionContextSerializer.php +++ b/Neos.Fusion/Classes/Core/Cache/FusionContextSerializer.php @@ -9,7 +9,7 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** - * Serializer for Fusion's [at]cache.context values + * Serializer for Fusion's \@cache.context values * * Uses the Flows's property mapper as implementation. * It relies on a converter being available from the context value type to string and reverse. diff --git a/Neos.Fusion/Classes/Core/FusionGlobals.php b/Neos.Fusion/Classes/Core/FusionGlobals.php index 3c1e26ca277..0b81b7f27bb 100644 --- a/Neos.Fusion/Classes/Core/FusionGlobals.php +++ b/Neos.Fusion/Classes/Core/FusionGlobals.php @@ -5,21 +5,31 @@ namespace Neos\Fusion\Core; /** - * Fusion allows to add variable to the context either via - * \@context.foo = "bar" or by leveraging the php api {@see Runtime::pushContext()}. + * Fusion differentiates between dynamic context variables and fixed Fusion globals. * - * Those approaches are highly dynamic and don't guarantee the existence of variables, + * Context variables are allowed to be set via Fusion's \@context.foo = "bar" + * or by leveraging the php api {@see Runtime::pushContext()}. + * + * Context variables are highly dynamic and don't guarantee the existence of a specific variables, * as they have to be explicitly preserved in uncached \@cache segments, * or might accidentally be popped from the stack. * - * The Fusion runtime is instantiated with a set of global variables which contain the EEL helper definitions - * or functions like FlowQuery. Also, variables like "request" are made available via it. + * The Fusion globals are immutable and part of the runtime's constructor. + * A fixed set of global variables which might contain the EEL helper definitions + * or functions like FlowQuery can be passed this way. + * + * Additionally, also special variables like "request" are made available. * - * The "${request}" special case: To make the request available in uncached segments, it would need to be serialized, - * but we don't allow this currently and despite that, it would be absurd to cache a random request. + * The speciality with "request" and similar is that they should be always available but never cached. + * Regular context variables must be serialized to be available in uncached segments, + * but the current request must not be serialized into the cache as it contains user specific information. * This is avoided by always exposing the current action request via the global variable. * * Overriding Fusion globals is disallowed via \@context and {@see Runtime::pushContext()}. + * + * Fusion globals are case-sensitive, though it's not recommend to leverage this behaviour. + * + * @internal The globals will be set inside the FusionView as declared */ final readonly class FusionGlobals { @@ -45,8 +55,13 @@ public static function fromArray(array $variables): self } /** - * You can access the current request like via this getter: - * `$runtime->fusionGlobals->get('request')` + * Access the possible current request or other globals: + * + * $actionRequest = $this->runtime->fusionGlobals->get('request'); + * if (!$actionRequest instanceof ActionRequest) { + * // fallback or error + * } + * */ public function get(string $name): mixed { diff --git a/Neos.Fusion/Classes/Core/RuntimeFactory.php b/Neos.Fusion/Classes/Core/RuntimeFactory.php index 252c45170df..fc5d84bd757 100644 --- a/Neos.Fusion/Classes/Core/RuntimeFactory.php +++ b/Neos.Fusion/Classes/Core/RuntimeFactory.php @@ -45,16 +45,15 @@ public function create(array $fusionConfiguration, ControllerContext $controller $defaultContextVariables = EelUtility::getDefaultContextVariables( $this->defaultContextConfiguration ?? [] ); - $runtime = new Runtime( + return new Runtime( FusionConfiguration::fromArray($fusionConfiguration), FusionGlobals::fromArray( [ + ...$defaultContextVariables, 'request' => $controllerContext?->getRequest() ?? ActionRequest::fromHttpRequest(ServerRequest::fromGlobals()), - ...$defaultContextVariables ] ) ); - return $runtime; } public function createFromConfiguration( diff --git a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php index 0e50845b659..03ca0a2352f 100644 --- a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php +++ b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php @@ -16,18 +16,16 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** - * Serializer for Fusion's [at]cache.context values + * Serializer for Fusion's \@cache.context values * * Implements special handing for serializing {@see Node} objects in fusions cache context: * - * ``` - * [at]cache { - * mode = 'uncached' - * context { - * 1 = 'node' - * } - * } - * ``` + * \@cache { + * mode = 'uncached' + * context { + * 1 = 'node' + * } + * } * * The property mapper cannot be relied upon to serialize nodes, as this is willingly not implemented. * diff --git a/Neos.Neos/Classes/View/FusionView.php b/Neos.Neos/Classes/View/FusionView.php index 446771cae37..629760b14b7 100644 --- a/Neos.Neos/Classes/View/FusionView.php +++ b/Neos.Neos/Classes/View/FusionView.php @@ -18,6 +18,8 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; +use Neos\Flow\Mvc\ActionRequest; +use Neos\Flow\Mvc\Controller\ControllerContext; use Neos\Flow\Mvc\View\AbstractView; use Neos\Flow\Security\Context; use Neos\Fusion\Core\FusionGlobals; @@ -52,6 +54,12 @@ class FusionView extends AbstractView #[Flow\Inject] protected RenderingModeService $renderingModeService; + /** + * Via {@see assign} request using the "request" key, + * will be available also as Fusion global in the runtime. + */ + protected ?ActionRequest $assignedActionRequest = null; + /** * Renders the view * @@ -198,10 +206,10 @@ protected function getFusionRuntime(Node $currentSiteNode) $renderingMode = $this->renderingModeService->findByName($this->getOption('renderingModeName')); - $fusionGlobals = FusionGlobals::fromArray([ - 'request' => $this->controllerContext->getRequest(), + $fusionGlobals = FusionGlobals::fromArray(array_filter([ + 'request' => $this->assignedActionRequest, 'renderingMode' => $renderingMode - ]); + ])); $this->fusionRuntime = $this->runtimeFactory->createFromConfiguration( $fusionConfiguration, $fusionGlobals @@ -222,7 +230,30 @@ protected function getFusionRuntime(Node $currentSiteNode) */ public function assign($key, $value): AbstractView { + if ($key === 'request') { + // the request cannot be used as "normal" fusion variable and must be treated as FusionGlobal + // to for example not cache it accidentally + // additionally we need it for special request based handling in the view + $this->assignedActionRequest = $value; + return $this; + } $this->fusionRuntime = null; return parent::assign($key, $value); } + + /** + * Legacy layer to set the request for this view if not set already. + * + * Please use {@see assign} with "request" instead + * + * $view->assign('request"', $this->request) + * + * @deprecated with Neos 9 + */ + public function setControllerContext(ControllerContext $controllerContext) + { + if (!$this->assignedActionRequest) { + $this->assignedActionRequest = $controllerContext->getRequest(); + } + } } From 51fb7f7850ef2d24e09ab82d600898820c55627a Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 15 Mar 2024 09:44:58 +0100 Subject: [PATCH 063/214] TASK: Adjust views to stricter return types --- .../Classes/View/FusionExceptionView.php | 19 ++++++------------- .../Classes/View/Service/AssetJsonView.php | 7 ++++--- .../Classes/View/Service/NodeJsonView.php | 7 ++++--- .../View/Service/WorkspaceJsonView.php | 7 ++++--- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/Neos.Neos/Classes/View/FusionExceptionView.php b/Neos.Neos/Classes/View/FusionExceptionView.php index c70f22ee50f..821c25eb275 100644 --- a/Neos.Neos/Classes/View/FusionExceptionView.php +++ b/Neos.Neos/Classes/View/FusionExceptionView.php @@ -36,6 +36,8 @@ use Neos\Neos\Domain\Service\SiteNodeUtility; use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionFailedException; use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; class FusionExceptionView extends AbstractView { @@ -87,7 +89,7 @@ class FusionExceptionView extends AbstractView #[Flow\Inject] protected DomainRepository $domainRepository; - public function render(): string + public function render(): ResponseInterface|StreamInterface { $requestHandler = $this->bootstrap->getActiveRequestHandler(); @@ -141,7 +143,7 @@ public function render(): string $this->setFallbackRuleFromDimension($dimensionSpacePoint); - $httpResponse = $fusionRuntime->renderResponse('error', array_merge( + return $fusionRuntime->renderResponse('error', array_merge( $this->variables, [ 'node' => $currentSiteNode, @@ -149,14 +151,6 @@ public function render(): string 'site' => $currentSiteNode ] )); - - /** - * Workaround: The http status code will already be sent and - * Flow's {@see \Neos\Flow\Error\DebugExceptionHandler::echoExceptionWeb()} - * expects a view to return a string to be echo'd. - * Thus, we unwrap the repose here: - */ - return $httpResponse->getBody()->getContents(); } protected function getFusionRuntime( @@ -184,7 +178,7 @@ protected function getFusionRuntime( return $this->fusionRuntime; } - private function renderErrorWelcomeScreen(): string + private function renderErrorWelcomeScreen(): ResponseInterface|StreamInterface { // in case no neos site being there or no site node we cannot continue with the fusion exception view, // as we wouldn't know the site and cannot get the site's root.fusion @@ -196,7 +190,6 @@ private function renderErrorWelcomeScreen(): string 'enableContentCache' => false, ]); $view->assignMultiple($this->variables); - $output = $view->render(); - return $output->getBody()->getContents(); + return $view->render(); } } diff --git a/Neos.Neos/Classes/View/Service/AssetJsonView.php b/Neos.Neos/Classes/View/Service/AssetJsonView.php index bb15b6306e5..c56c0c596ec 100644 --- a/Neos.Neos/Classes/View/Service/AssetJsonView.php +++ b/Neos.Neos/Classes/View/Service/AssetJsonView.php @@ -16,12 +16,15 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\View\JsonView; +use Psr\Http\Message\ResponseInterface; /** * A view specialised on a JSON representation of Assets. * * This view is used by the service controllers in Neos\Neos\Controller\Service\ * + * @deprecated with Neos 9, the JsonView should not be used + * @internal only to be used internally * @Flow\Scope("prototype") */ class AssetJsonView extends JsonView @@ -29,10 +32,8 @@ class AssetJsonView extends JsonView /** * Configures rendering according to the set variable(s) and calls * render on the parent. - * - * @return string */ - public function render() + public function render(): ResponseInterface { if (isset($this->variables['assets'])) { $this->setConfiguration( diff --git a/Neos.Neos/Classes/View/Service/NodeJsonView.php b/Neos.Neos/Classes/View/Service/NodeJsonView.php index 24ce05ea747..b4a10ca6ff8 100644 --- a/Neos.Neos/Classes/View/Service/NodeJsonView.php +++ b/Neos.Neos/Classes/View/Service/NodeJsonView.php @@ -16,12 +16,15 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\View\JsonView; +use Psr\Http\Message\ResponseInterface; /** * A view specialised on a JSON representation of Nodes. * * This view is used by the service controllers in Neos\Neos\Controller\Service\ * + * @deprecated with Neos 9, the JsonView should not be used + * @internal only to be used internally * @Flow\Scope("prototype") */ class NodeJsonView extends JsonView @@ -29,10 +32,8 @@ class NodeJsonView extends JsonView /** * Configures rendering according to the set variable(s) and calls * render on the parent. - * - * @return string */ - public function render() + public function render(): ResponseInterface { if (isset($this->variables['nodes'])) { $this->setConfiguration( diff --git a/Neos.Neos/Classes/View/Service/WorkspaceJsonView.php b/Neos.Neos/Classes/View/Service/WorkspaceJsonView.php index 52c8b1e261e..04dd5467ad8 100644 --- a/Neos.Neos/Classes/View/Service/WorkspaceJsonView.php +++ b/Neos.Neos/Classes/View/Service/WorkspaceJsonView.php @@ -16,12 +16,15 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\View\JsonView; +use Psr\Http\Message\ResponseInterface; /** * A view specialised on a JSON representation of Workspaces. * * This view is used by the service controllers in Neos\Neos\Controller\Service\ * + * @deprecated with Neos 9, the JsonView should not be used + * @internal only to be used internally * @Flow\Scope("prototype") */ class WorkspaceJsonView extends JsonView @@ -29,10 +32,8 @@ class WorkspaceJsonView extends JsonView /** * Configures rendering according to the set variable(s) and calls * render on the parent. - * - * @return string */ - public function render() + public function render(): ResponseInterface { if (isset($this->variables['workspaces'])) { $this->setConfiguration( From e1a2678b1f0c76c7c958193a753af7fc45d49112 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 15 Mar 2024 11:06:06 +0100 Subject: [PATCH 064/214] TASK: Return `ResponseInterface` or `StreamInterface` in fusion view - this will make the change less breaking due to allowing an implicit string cast - adjust to fluidViews will now return a stream --- Neos.Fusion/Classes/Core/Runtime.php | 40 +++++++++---- .../FusionObjects/TemplateImplementation.php | 2 +- Neos.Fusion/Classes/View/FusionView.php | 10 ++-- .../AbstractFusionObjectTest.php | 18 +----- .../Tests/Functional/View/FusionViewTest.php | 13 ++-- Neos.Fusion/Tests/Unit/Core/RuntimeTest.php | 59 +++++++++++-------- .../Fusion/ExceptionHandlers/PageHandler.php | 2 +- .../Classes/View/FusionExceptionView.php | 2 +- Neos.Neos/Classes/View/FusionView.php | 9 +-- .../ViewHelpers/StandaloneViewViewHelper.php | 2 +- 10 files changed, 87 insertions(+), 70 deletions(-) diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index fede8e27af6..f9fca878474 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -13,6 +13,7 @@ use GuzzleHttp\Psr7\Message; use GuzzleHttp\Psr7\Response; +use Neos\Http\Factories\StreamFactoryTrait; use Psr\Http\Message\ResponseInterface; use Neos\Eel\Utility as EelUtility; use Neos\Flow\Annotations as Flow; @@ -33,6 +34,7 @@ use Neos\Utility\Arrays; use Neos\Utility\ObjectAccess; use Neos\Utility\PositionalArraySorter; +use Psr\Http\Message\StreamInterface; /** * Fusion Runtime @@ -58,6 +60,8 @@ */ class Runtime { + use StreamFactoryTrait; + /** * Internal constants defining how evaluate should work in case of an error */ @@ -265,13 +269,18 @@ public function getLastEvaluationStatus() } /** - * Entry point to render a Fusion path as HttpResponse. + * Entry point to render a Fusion path with the context. + * + * A ResponseInterface will be returned, if a Neos.Fusion:Http.Message was defined in the entry path, + * or if Neos.Fusion.Form or Neos.Neos:Plugin were used in the path. + * + * In all other simple cases a StreamInterface will be returned. * * @param string $entryFusionPath the absolute fusion path to render (without leading slash) * @param array $contextVariables the context variables that will be available during the rendering. * @throws IllegalEntryFusionPathValueException The Fusion path rendered to a value that is not a compatible http response body: string|\Stringable|null */ - public function renderResponse(string $entryFusionPath, array $contextVariables): ResponseInterface + public function renderEntryPathWithContext(string $entryFusionPath, array $contextVariables): ResponseInterface|StreamInterface { // Like in pushContext, we don't allow to overrule fusion globals foreach ($contextVariables as $key => $_) { @@ -298,11 +307,16 @@ public function renderResponse(string $entryFusionPath, array $contextVariables) return Message::parseResponse($output); } + if ($output instanceof StreamInterface) { + // if someone manages to return a stream *g + return $output; + } + if (!is_string($output) && !$output instanceof \Stringable && $output !== null) { throw new IllegalEntryFusionPathValueException(sprintf('Fusion entry path "%s" is expected to render a compatible http response body: string|\Stringable|null. Got %s instead.', $entryFusionPath, get_debug_type($output)), 1706454898); } - return new Response(body: $output); + return $this->createStream((string)$output); }); } @@ -944,9 +958,9 @@ protected function throwExceptionForUnrenderablePathIfNeeded($fusionPath, $fusio * While HIGHLY internal behaviour and ONLY to be used by Neos.Fusion.Form or Neos.Neos:Plugin * this legacy layer is in place still allows this functionality. * - * @param \Closure(): ResponseInterface $renderer + * @param \Closure(): (ResponseInterface|StreamInterface) $renderer */ - private function withSimulatedLegacyControllerContext(\Closure $renderer): ResponseInterface + private function withSimulatedLegacyControllerContext(\Closure $renderer): ResponseInterface|StreamInterface { if ($this->legacyActionResponseForCurrentRendering !== null) { throw new Exception('Recursion detected in `Runtime::renderResponse`. This entry point is only allowed to be invoked once per rendering.', 1706993940); @@ -955,7 +969,7 @@ private function withSimulatedLegacyControllerContext(\Closure $renderer): Respo // actual rendering try { - $httpResponse = $renderer(); + $httpResponseOrStream = $renderer(); } finally { $toBeMergedLegacyActionResponse = $this->legacyActionResponseForCurrentRendering; // reset for next render @@ -964,14 +978,20 @@ private function withSimulatedLegacyControllerContext(\Closure $renderer): Respo // transfer possible headers that have been set dynamically foreach ($toBeMergedLegacyActionResponse->buildHttpResponse()->getHeaders() as $name => $values) { - $httpResponse = $httpResponse->withAddedHeader($name, $values); + if ($httpResponseOrStream instanceof StreamInterface) { + $httpResponseOrStream = new Response(body: $httpResponseOrStream); + } + $httpResponseOrStream = $httpResponseOrStream->withAddedHeader($name, $values); } // if the status code is 200 we assume it's the default and will not overrule it if ($toBeMergedLegacyActionResponse->getStatusCode() !== 200) { - $httpResponse = $httpResponse->withStatus($toBeMergedLegacyActionResponse->getStatusCode()); + if ($httpResponseOrStream instanceof StreamInterface) { + $httpResponseOrStream = new Response(body: $httpResponseOrStream); + } + $httpResponseOrStream = $httpResponseOrStream->withStatus($toBeMergedLegacyActionResponse->getStatusCode()); } - return $httpResponse; + return $httpResponseOrStream; } /** @@ -981,7 +1001,7 @@ private function withSimulatedLegacyControllerContext(\Closure $renderer): Respo * * WARNING: * Invoking this backwards-compatible layer is possibly unsafe, if the rendering was not started - * in {@see self::renderResponse()} or no `request` global is available. This will raise an exception. + * in {@see self::renderEntryPathWithContext()} or no `request` global is available. This will raise an exception. * * @deprecated with Neos 9.0 * @internal diff --git a/Neos.Fusion/Classes/FusionObjects/TemplateImplementation.php b/Neos.Fusion/Classes/FusionObjects/TemplateImplementation.php index 4ccc72f2381..edec81b35c9 100644 --- a/Neos.Fusion/Classes/FusionObjects/TemplateImplementation.php +++ b/Neos.Fusion/Classes/FusionObjects/TemplateImplementation.php @@ -140,7 +140,7 @@ public function evaluate() if ($sectionName !== null) { return $fluidTemplate->renderSection($sectionName); } else { - return $fluidTemplate->render(); + return $fluidTemplate->render()->getContents(); } } diff --git a/Neos.Fusion/Classes/View/FusionView.php b/Neos.Fusion/Classes/View/FusionView.php index 1bb5439d0fc..358b8f65b65 100644 --- a/Neos.Fusion/Classes/View/FusionView.php +++ b/Neos.Fusion/Classes/View/FusionView.php @@ -23,6 +23,7 @@ use Neos\Fusion\Core\Runtime; use Neos\Fusion\Core\RuntimeFactory; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; /** * View for using Fusion for standard MVC controllers. @@ -173,15 +174,16 @@ public function setFusionPathPatterns(array $pathPatterns) } /** - * Render the view + * Render the view to a full response in case a Neos.Fusion:Http.Message was used. + * If the fusion path contains a simple string a stream will be rendered. * - * @return ResponseInterface The rendered view + * @return ResponseInterface|StreamInterface * @api */ - public function render(): ResponseInterface + public function render(): ResponseInterface|StreamInterface { $this->initializeFusionRuntime(); - return $this->fusionRuntime->renderResponse( + return $this->fusionRuntime->renderEntryPathWithContext( $this->getFusionPathForCurrentRequest(), $this->variables ); diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php b/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php index 94cfca2874d..d750745cf49 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php +++ b/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php @@ -13,8 +13,6 @@ use GuzzleHttp\Psr7\ServerRequest; use Neos\Flow\Mvc\ActionRequest; -use Neos\Flow\Mvc\Controller\ControllerContext; -use Neos\Flow\Mvc\View\ViewInterface; use Neos\Flow\Tests\FunctionalTestCase; use Neos\Fusion\Core\FusionGlobals; use Neos\Fusion\Core\FusionSourceCodeCollection; @@ -46,8 +44,6 @@ abstract class AbstractFusionObjectTest extends FunctionalTestCase * Instead we want to refactor our tests to behat at some point. * * Thus the hack. - * - * @return ViewInterface */ protected function buildView() { @@ -62,7 +58,7 @@ protected function buildView() $runtime->pushContext('fixtureDirectory', __DIR__ . '/Fixtures/'); // todo rewrite everything as behat test :D - return new class($runtime) implements ViewInterface { + return new class($runtime) { private string $fusionPath; public function __construct( private readonly Runtime $runtime @@ -97,18 +93,6 @@ public function render() throw $e->getWrappedException(); } } - public static function createWithOptions(array $options) - { - throw new \BadMethodCallException(); - } - public function setControllerContext(ControllerContext $controllerContext) - { - throw new \BadMethodCallException(); - } - public function canRender(ControllerContext $controllerContext) - { - throw new \BadMethodCallException(); - } }; } diff --git a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php index f59dad2cf69..9c7025c36a0 100644 --- a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php +++ b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php @@ -16,6 +16,7 @@ use Neos\Fusion\Core\IllegalEntryFusionPathValueException; use Neos\Fusion\View\FusionView; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; /** * Testcase for the Fusion View @@ -29,7 +30,7 @@ class FusionViewTest extends FunctionalTestCase public function fusionViewIsUsedForRendering() { $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); - self::assertEquals('X', $view->render()->getBody()->getContents()); + self::assertEquals('X', $view->render()->getContents()); } /** @@ -39,7 +40,7 @@ public function fusionViewUsesGivenPathIfSet() { $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); $view->setFusionPath('foo/bar'); - self::assertEquals('Xfoobar', $view->render()->getBody()->getContents()); + self::assertEquals('Xfoobar', $view->render()->getContents()); } /** @@ -49,19 +50,19 @@ public function fusionViewOutputsVariable() { $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); $view->assign('test', 'Hallo Welt'); - self::assertEquals('XHallo Welt', $view->render()->getBody()->getContents()); + self::assertEquals('XHallo Welt', $view->render()->getContents()); } /** * @test */ - public function fusionVieReturnsHttpResponse() + public function fusionVieReturnsStreamInterface() { $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); $view->assign('test', 'Hallo Welt'); $response = $view->render(); - self::assertInstanceOf(ResponseInterface::class, $response); - self::assertEquals('XHallo Welt', $view->render()->getBody()->getContents()); + self::assertInstanceOf(StreamInterface::class, $response); + self::assertEquals('XHallo Welt', $response->getContents()); } /** diff --git a/Neos.Fusion/Tests/Unit/Core/RuntimeTest.php b/Neos.Fusion/Tests/Unit/Core/RuntimeTest.php index 4716d1f6cbd..17125933372 100644 --- a/Neos.Fusion/Tests/Unit/Core/RuntimeTest.php +++ b/Neos.Fusion/Tests/Unit/Core/RuntimeTest.php @@ -25,6 +25,7 @@ use Neos\Fusion\Exception\RuntimeException; use Neos\Fusion\FusionObjects\ValueImplementation; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; class RuntimeTest extends UnitTestCase { @@ -219,7 +220,7 @@ public function renderResponseIsNotAllowedToOverrideFusionGlobals() $this->expectExceptionMessage('Overriding Fusion global variable "request" via @context is not allowed.'); $runtime = new Runtime(FusionConfiguration::fromArray([]), FusionGlobals::fromArray(['request' => 'fixed'])); - $runtime->renderResponse('foo', ['request' =>'anything']); + $runtime->renderEntryPathWithContext('foo', ['request' =>'anything']); } /** @@ -238,15 +239,11 @@ public function pushContextArrayIsAllowedToOverrideFusionGlobals() self::assertTrue(true); } - public static function renderResponseExamples(): iterable + public static function renderStreamExamples(): iterable { yield 'simple string' => [ 'rawValue' => 'my string', - 'response' => <<<'TEXT' - HTTP/1.1 200 OK - - my string - TEXT + 'streamContents' => 'my string' ]; yield 'string cast object (\Stringable)' => [ @@ -256,31 +253,22 @@ public function __toString() return 'my string karsten'; } }, - 'response' => <<<'TEXT' - HTTP/1.1 200 OK - - my string karsten - TEXT + 'streamContents' => 'my string karsten' ]; yield 'empty string' => [ 'rawValue' => '', - 'response' => <<<'TEXT' - HTTP/1.1 200 OK - - - TEXT + 'streamContents' => '' ]; yield 'null value' => [ 'rawValue' => null, - 'response' => <<<'TEXT' - HTTP/1.1 200 OK - - - TEXT + 'streamContents' => '' ]; + } + public function renderResponseExamples(): iterable + { yield 'stringified http response string is upcasted' => [ 'rawValue' => <<<'TEXT' HTTP/1.1 418 OK @@ -303,11 +291,32 @@ public function __toString() ]; } + /** + * @test + * @dataProvider renderStreamExamples + */ + public function renderEntryPathStream(mixed $rawValue, string $expectedStreamContents) + { + $runtime = $this->getMockBuilder(Runtime::class) + ->setConstructorArgs([FusionConfiguration::fromArray([]), FusionGlobals::empty()]) + ->onlyMethods(['render']) + ->getMock(); + + $runtime->expects(self::once())->method('render')->willReturn( + $rawValue + ); + + $response = $runtime->renderEntryPathWithContext('path', []); + + self::assertInstanceOf(StreamInterface::class, $response); + self::assertSame($expectedStreamContents, $response->getContents()); + } + /** * @test * @dataProvider renderResponseExamples */ - public function renderResponse(mixed $rawValue, string $expectedHttpResponseString) + public function renderEntryPathResponse(mixed $rawValue, string $expectedHttpResponseString) { $runtime = $this->getMockBuilder(Runtime::class) ->setConstructorArgs([FusionConfiguration::fromArray([]), FusionGlobals::empty()]) @@ -318,7 +327,7 @@ public function renderResponse(mixed $rawValue, string $expectedHttpResponseStri is_string($rawValue) ? str_replace("\n", "\r\n", $rawValue) : $rawValue ); - $response = $runtime->renderResponse('path', []); + $response = $runtime->renderEntryPathWithContext('path', []); self::assertInstanceOf(ResponseInterface::class, $response); self::assertSame(str_replace("\n", "\r\n", $expectedHttpResponseString), Message::toString($response)); @@ -371,6 +380,6 @@ public function renderResponseThrowsIfNotStringable(mixed $illegalValue) $illegalValue ); - $runtime->renderResponse('path', []); + $runtime->renderEntryPathWithContext('path', []); } } diff --git a/Neos.Neos/Classes/Fusion/ExceptionHandlers/PageHandler.php b/Neos.Neos/Classes/Fusion/ExceptionHandlers/PageHandler.php index c20eca8bcda..bf47fbeea60 100644 --- a/Neos.Neos/Classes/Fusion/ExceptionHandlers/PageHandler.php +++ b/Neos.Neos/Classes/Fusion/ExceptionHandlers/PageHandler.php @@ -105,7 +105,7 @@ protected function handle($fusionPath, \Exception $exception, $referenceCode) 'node' => $node ]); - return $this->wrapHttpResponse($exception, $fluidView->render()); + return $this->wrapHttpResponse($exception, $fluidView->render()->getContents()); } /** diff --git a/Neos.Neos/Classes/View/FusionExceptionView.php b/Neos.Neos/Classes/View/FusionExceptionView.php index 821c25eb275..eb675b7310d 100644 --- a/Neos.Neos/Classes/View/FusionExceptionView.php +++ b/Neos.Neos/Classes/View/FusionExceptionView.php @@ -143,7 +143,7 @@ public function render(): ResponseInterface|StreamInterface $this->setFallbackRuleFromDimension($dimensionSpacePoint); - return $fusionRuntime->renderResponse('error', array_merge( + return $fusionRuntime->renderEntryPathWithContext('error', array_merge( $this->variables, [ 'node' => $currentSiteNode, diff --git a/Neos.Neos/Classes/View/FusionView.php b/Neos.Neos/Classes/View/FusionView.php index 629760b14b7..8c21bf35eca 100644 --- a/Neos.Neos/Classes/View/FusionView.php +++ b/Neos.Neos/Classes/View/FusionView.php @@ -33,6 +33,7 @@ use Neos\Neos\Exception; use Neos\Neos\Utility\NodeTypeWithFallbackProvider; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; /** * A Fusion view for Neos @@ -61,13 +62,13 @@ class FusionView extends AbstractView protected ?ActionRequest $assignedActionRequest = null; /** - * Renders the view + * Render the view to a full response in case a Neos.Fusion:Http.Message was used. + * If the fusion path contains a simple string a stream will be rendered. * - * @return ResponseInterface The rendered view * @throws \Exception if no node is given * @api */ - public function render(): ResponseInterface + public function render(): ResponseInterface|StreamInterface { $currentNode = $this->getCurrentNode(); @@ -82,7 +83,7 @@ public function render(): ResponseInterface $this->setFallbackRuleFromDimension($currentNode->subgraphIdentity->dimensionSpacePoint); - return $fusionRuntime->renderResponse($this->fusionPath, [ + return $fusionRuntime->renderEntryPathWithContext($this->fusionPath, [ 'node' => $currentNode, 'documentNode' => $this->getClosestDocumentNode($currentNode) ?: $currentNode, 'site' => $currentSiteNode diff --git a/Neos.Neos/Classes/ViewHelpers/StandaloneViewViewHelper.php b/Neos.Neos/Classes/ViewHelpers/StandaloneViewViewHelper.php index 8408f9a5cb6..4851341cf56 100644 --- a/Neos.Neos/Classes/ViewHelpers/StandaloneViewViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/StandaloneViewViewHelper.php @@ -74,6 +74,6 @@ public function render(): string { $standaloneView = new StandaloneView($this->controllerContext->getRequest()); $standaloneView->setTemplatePathAndFilename($this->arguments['templatePathAndFilename']); - return $standaloneView->assignMultiple($this->arguments['arguments'])->render(); + return $standaloneView->assignMultiple($this->arguments['arguments'])->render()->getContents(); } } From a99de6ca9569e32d5ff8153acbccd15832bca391 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 17 Mar 2024 22:39:15 +0100 Subject: [PATCH 065/214] TASK: Adjust to strict types in `ViewInterface` --- Neos.Fusion/Classes/View/FusionView.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Neos.Fusion/Classes/View/FusionView.php b/Neos.Fusion/Classes/View/FusionView.php index 358b8f65b65..d6c9c407ac2 100644 --- a/Neos.Fusion/Classes/View/FusionView.php +++ b/Neos.Fusion/Classes/View/FusionView.php @@ -103,7 +103,10 @@ public function setOption($optionName, $value) parent::setOption($optionName, $value); } - public function assign($key, $value) + /** + * @return $this for chaining + */ + public function assign(string $key, mixed $value): self { if ($key === 'request') { // the request cannot be used as "normal" fusion variable and must be treated as FusionGlobal From 716e6a8c35059fed38c595c446ee3386b8e5affa Mon Sep 17 00:00:00 2001 From: Marc Henry Schultz <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:42:27 +0200 Subject: [PATCH 066/214] Fix typos Co-authored-by: Wilhelm Behncke <2522299+grebaldi@users.noreply.github.com> --- Neos.Fusion/Classes/Core/LegacyFusionControllerContext.php | 4 ++-- Neos.Fusion/Classes/Core/Runtime.php | 5 +++-- .../Functional/FusionObjects/AbstractFusionObjectTest.php | 2 +- Neos.Fusion/Tests/Functional/View/FusionViewTest.php | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Neos.Fusion/Classes/Core/LegacyFusionControllerContext.php b/Neos.Fusion/Classes/Core/LegacyFusionControllerContext.php index 07d02d0e6f1..1196724b5b1 100644 --- a/Neos.Fusion/Classes/Core/LegacyFusionControllerContext.php +++ b/Neos.Fusion/Classes/Core/LegacyFusionControllerContext.php @@ -105,10 +105,10 @@ public function getFlashMessageContainer(): FlashMessageContainer * * Gives access to the legacy mutable action response simulation {@see Runtime::withSimulatedLegacyControllerContext()} * - * Initially it was possible to mutate the current response of the active MVC controller though this getter. + * Initially it was possible to mutate the current response of the active MVC controller through this getter. * * While *HIGHLY* internal behaviour and *ONLY* to be used by Neos.Fusion.Form or Neos.Neos:Plugin - * this legacy layer is in place still allows this functionality. + * this legacy layer is in place to still allow this functionality. * * @deprecated with Neos 9.0 can be removed with 10 * @internal THIS SHOULD NEVER BE CALLED ON USER-LAND diff --git a/Neos.Fusion/Classes/Core/Runtime.php b/Neos.Fusion/Classes/Core/Runtime.php index f9fca878474..76f12b0d016 100644 --- a/Neos.Fusion/Classes/Core/Runtime.php +++ b/Neos.Fusion/Classes/Core/Runtime.php @@ -954,9 +954,9 @@ protected function throwExceptionForUnrenderablePathIfNeeded($fusionPath, $fusio /** * Implements the legacy controller context simulation {@see self::getControllerContext()} * - * Initially it was possible to mutate the current response of the active MVC controller though $response. + * Initially it was possible to mutate the current response of the active MVC controller through $response. * While HIGHLY internal behaviour and ONLY to be used by Neos.Fusion.Form or Neos.Neos:Plugin - * this legacy layer is in place still allows this functionality. + * this legacy layer is in place to still allow this functionality. * * @param \Closure(): (ResponseInterface|StreamInterface) $renderer */ @@ -968,6 +968,7 @@ private function withSimulatedLegacyControllerContext(\Closure $renderer): Respo $this->legacyActionResponseForCurrentRendering = new ActionResponse(); // actual rendering + $httpResponseOrStream = null; try { $httpResponseOrStream = $renderer(); } finally { diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php b/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php index d750745cf49..fabf9962426 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php +++ b/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php @@ -35,7 +35,7 @@ abstract class AbstractFusionObjectTest extends FunctionalTestCase /** * TODO THIS IS HACKY AS WE CREATE AN OWN VIEW * - * We do that as the FusionView (rightfully) does return mixed anymore. + * We do that as the FusionView (rightfully) doesn't return mixed anymore. * * We could instead also rewrite all tests to use the Runtime instead. * diff --git a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php index 9c7025c36a0..e15f978159a 100644 --- a/Neos.Fusion/Tests/Functional/View/FusionViewTest.php +++ b/Neos.Fusion/Tests/Functional/View/FusionViewTest.php @@ -56,7 +56,7 @@ public function fusionViewOutputsVariable() /** * @test */ - public function fusionVieReturnsStreamInterface() + public function fusionViewReturnsStreamInterface() { $view = $this->buildView('Foo\Bar\Controller\TestController', 'index'); $view->assign('test', 'Hallo Welt'); From 2cf100f5aee0d3fbdecfb3abd648618cdd5407a8 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:07:24 +0200 Subject: [PATCH 067/214] TASK: Introduce `TestingViewForFusionRuntime` for testing instead of using a hacky anonymous class --- .../AbstractFusionObjectTest.php | 61 ++--------------- .../TestingViewForFusionRuntime.php | 67 +++++++++++++++++++ .../Functional/Fusion/NodeHelperTest.php | 3 +- 3 files changed, 73 insertions(+), 58 deletions(-) create mode 100644 Neos.Fusion/Tests/Functional/FusionObjects/TestingViewForFusionRuntime.php diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php b/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php index fabf9962426..452b61d6e6e 100644 --- a/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php +++ b/Neos.Fusion/Tests/Functional/FusionObjects/AbstractFusionObjectTest.php @@ -16,10 +16,7 @@ use Neos\Flow\Tests\FunctionalTestCase; use Neos\Fusion\Core\FusionGlobals; use Neos\Fusion\Core\FusionSourceCodeCollection; -use Neos\Fusion\Core\Runtime; use Neos\Fusion\Core\RuntimeFactory; -use Neos\Fusion\Exception\RuntimeException; -use Psr\Http\Message\ServerRequestFactoryInterface; /** * Testcase for the Fusion View @@ -32,22 +29,8 @@ abstract class AbstractFusionObjectTest extends FunctionalTestCase */ protected $request; - /** - * TODO THIS IS HACKY AS WE CREATE AN OWN VIEW - * - * We do that as the FusionView (rightfully) doesn't return mixed anymore. - * - * We could instead also rewrite all tests to use the Runtime instead. - * - * But that would be a lot of effort for nothing. - * - * Instead we want to refactor our tests to behat at some point. - * - * Thus the hack. - */ - protected function buildView() + protected function buildView(): TestingViewForFusionRuntime { - /** @var ServerRequestFactoryInterface $httpRequestFactory */ $this->request = ActionRequest::fromHttpRequest(new ServerRequest('GET', 'http://localhost/')); $runtime = $this->objectManager->get(RuntimeFactory::class)->createFromSourceCode( @@ -55,45 +38,9 @@ protected function buildView() FusionGlobals::fromArray(['request' => $this->request]) ); - $runtime->pushContext('fixtureDirectory', __DIR__ . '/Fixtures/'); - - // todo rewrite everything as behat test :D - return new class($runtime) { - private string $fusionPath; - public function __construct( - private readonly Runtime $runtime - ) { - } - public function setFusionPath(string $fusionPath) - { - $this->fusionPath = $fusionPath; - } - public function assign($key, $value) - { - $this->runtime->pushContext($key, $value); - } - public function setOption($key, $value) - { - match ($key) { - 'enableContentCache' => $this->runtime->setEnableContentCache($value), - 'debugMode' => $this->runtime->setDebugMode($value) - }; - } - public function assignMultiple(array $values) - { - foreach ($values as $key => $value) { - $this->runtime->pushContext($key, $value); - } - } - public function render() - { - try { - return $this->runtime->render($this->fusionPath); - } catch (RuntimeException $e) { - throw $e->getWrappedException(); - } - } - }; + $view = new TestingViewForFusionRuntime($runtime); + $view->assign('fixtureDirectory', __DIR__ . '/Fixtures/'); + return $view; } /** diff --git a/Neos.Fusion/Tests/Functional/FusionObjects/TestingViewForFusionRuntime.php b/Neos.Fusion/Tests/Functional/FusionObjects/TestingViewForFusionRuntime.php new file mode 100644 index 00000000000..aa03688ce27 --- /dev/null +++ b/Neos.Fusion/Tests/Functional/FusionObjects/TestingViewForFusionRuntime.php @@ -0,0 +1,67 @@ +fusionPath = $fusionPath; + } + + public function assign($key, $value) + { + $this->runtime->pushContext($key, $value); + } + + public function setOption($key, $value) + { + match ($key) { + 'enableContentCache' => $this->runtime->setEnableContentCache($value), + 'debugMode' => $this->runtime->setDebugMode($value) + }; + } + + public function assignMultiple(array $values) + { + foreach ($values as $key => $value) { + $this->runtime->pushContext($key, $value); + } + } + + public function render() + { + try { + return $this->runtime->render($this->fusionPath); + } catch (RuntimeException $e) { + throw $e->getWrappedException(); + } + } +} diff --git a/Neos.Neos/Tests/Functional/Fusion/NodeHelperTest.php b/Neos.Neos/Tests/Functional/Fusion/NodeHelperTest.php index 9952d6a776b..fc7b35fd719 100644 --- a/Neos.Neos/Tests/Functional/Fusion/NodeHelperTest.php +++ b/Neos.Neos/Tests/Functional/Fusion/NodeHelperTest.php @@ -30,6 +30,7 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\TestSuite\Unit\NodeSubjectProvider; use Neos\Fusion\Tests\Functional\FusionObjects\AbstractFusionObjectTest; +use Neos\Fusion\Tests\Functional\FusionObjects\TestingViewForFusionRuntime; use PHPUnit\Framework\MockObject\MockObject; /** @@ -107,7 +108,7 @@ public function crop() self::assertEquals('Some -', (string)$view->render()); } - protected function buildView() + protected function buildView(): TestingViewForFusionRuntime { $view = parent::buildView(); From fbc6ea95f05315dfaf2f7b58c6a398955a40177c Mon Sep 17 00:00:00 2001 From: Sebastian Helzle Date: Tue, 23 Apr 2024 15:03:01 +0200 Subject: [PATCH 068/214] BUGFIX: Add missing type converts for asset subtypes This prevents the raw data from being base64 encoded into the rendered output of the Neos backend and included in xhr requests from the Neos UI. Additionally the default settings for editor and constraints makes the usage of those affected types easier in nodetype properties. Resolves: #5006 --- Neos.Neos/Configuration/Settings.yaml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Neos.Neos/Configuration/Settings.yaml b/Neos.Neos/Configuration/Settings.yaml index 1f1a8cd094a..bbaa3f18b5c 100755 --- a/Neos.Neos/Configuration/Settings.yaml +++ b/Neos.Neos/Configuration/Settings.yaml @@ -201,6 +201,33 @@ Neos: features: upload: true mediaBrowser: true + Neos\Media\Domain\Model\Audio: + typeConverter: Neos\Neos\TypeConverter\EntityToIdentityConverter + editor: Neos.Neos/Inspector/Editors/AssetEditor + editorOptions: + features: + upload: true + mediaBrowser: true + constraints: + mediaTypes: + - 'audio/*' + Neos\Media\Domain\Model\Document: + typeConverter: Neos\Neos\TypeConverter\EntityToIdentityConverter + editor: Neos.Neos/Inspector/Editors/AssetEditor + editorOptions: + features: + upload: true + mediaBrowser: true + Neos\Media\Domain\Model\Video: + typeConverter: Neos\Neos\TypeConverter\EntityToIdentityConverter + editor: Neos.Neos/Inspector/Editors/AssetEditor + editorOptions: + features: + upload: true + mediaBrowser: true + constraints: + mediaTypes: + - 'video/*' array: typeConverter: Neos\Flow\Property\TypeConverter\TypedArrayConverter editor: Neos.Neos/Inspector/Editors/AssetEditor From cfaa24f4060b8ab5ab51385d1ca6511ce653bc95 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 24 Apr 2024 14:48:00 +0200 Subject: [PATCH 069/214] BUGFIX: Fix performance regression and content graph bug by partially reverting 4790 --- .../src/DoctrineDbalContentGraphSchemaBuilder.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 06cc35ff431..7722ff41f6f 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -66,7 +66,10 @@ private function createHierarchyRelationTable(): Table ]); return $table - ->setPrimaryKey(['childnodeanchor', 'contentstreamid', 'dimensionspacepointhash', 'parentnodeanchor']) + ->addIndex(['childnodeanchor']) + ->addIndex(['contentstreamid']) + ->addIndex(['parentnodeanchor']) + ->addIndex(['contentstreamid', 'childnodeanchor', 'dimensionspacepointhash']) ->addIndex(['contentstreamid', 'dimensionspacepointhash']); } From b1fcbccbb58d9dc6985fba51cdb575ec4b1d89ce Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Wed, 24 Apr 2024 16:05:19 +0200 Subject: [PATCH 070/214] FEATURE: Improved CLI output for projection replay Adds debugging infos to the output of the `cr:projectionReplay` command and nested progress bars to the output of `cr:projectionReplayAll` Related: #4777 --- .../Classes/Projection/Projections.php | 7 ++- .../Classes/Command/CrCommandController.php | 55 ++++++++++++++++--- .../Service/ProjectionReplayService.php | 17 ++++++ .../ProjectionReplayServiceFactory.php | 1 + 4 files changed, 71 insertions(+), 9 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Projection/Projections.php b/Neos.ContentRepository.Core/Classes/Projection/Projections.php index e9910356908..0bc7dffec20 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/Projections.php +++ b/Neos.ContentRepository.Core/Classes/Projection/Projections.php @@ -10,7 +10,7 @@ * @implements \IteratorAggregate * @internal */ -final class Projections implements \IteratorAggregate +final class Projections implements \IteratorAggregate, \Countable { /** * @var array>, ProjectionInterface> @@ -93,4 +93,9 @@ public function getIterator(): \Traversable { yield from $this->projections; } + + public function count(): int + { + return count($this->projections); + } } diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php index d78d6f92d82..b5d98b29e8e 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php +++ b/Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php @@ -13,6 +13,8 @@ use Neos\EventStore\Model\Event\SequenceNumber; use Neos\EventStore\Model\EventStore\StatusType; use Neos\Flow\Cli\CommandController; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\Output; final class CrCommandController extends CommandController @@ -116,6 +118,11 @@ public function statusCommand(string $contentRepository = 'default', bool $verbo */ public function projectionReplayCommand(string $projection, string $contentRepository = 'default', bool $force = false, bool $quiet = false, int $until = 0): void { + if ($quiet) { + $this->output->getOutput()->setVerbosity(Output::VERBOSITY_QUIET); + } + $progressBar = new ProgressBar($this->output->getOutput()); + $progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:16s%/%estimated:-16s% %memory:6s%'); if (!$force && $quiet) { $this->outputLine('Cannot run in quiet mode without --force. Please acknowledge that this command will reset and replay this projection. This may take some time.'); $this->quit(1); @@ -129,19 +136,18 @@ public function projectionReplayCommand(string $projection, string $contentRepos $contentRepositoryId = ContentRepositoryId::fromString($contentRepository); $projectionService = $this->contentRepositoryRegistry->buildService($contentRepositoryId, $this->projectionServiceFactory); + $options = CatchUpOptions::create(); if (!$quiet) { $this->outputLine('Replaying events for projection "%s" of Content Repository "%s" ...', [$projection, $contentRepositoryId->value]); - $this->output->progressStart(); + $progressBar->start(max($until > 0 ? $until : $projectionService->highestSequenceNumber()->value, 1)); + $options->with(progressCallback: fn () => $progressBar->advance()); } - $options = CatchUpOptions::create( - progressCallback: fn () => $this->output->progressAdvance(), - ); if ($until > 0) { $options = $options->with(maximumSequenceNumber: SequenceNumber::fromInteger($until)); } $projectionService->replayProjection($projection, $options); if (!$quiet) { - $this->output->progressFinish(); + $progressBar->finish(); $this->outputLine(); $this->outputLine('Done.'); } @@ -153,9 +159,23 @@ public function projectionReplayCommand(string $projection, string $contentRepos * @param string $contentRepository Identifier of the Content Repository instance to operate on * @param bool $force Replay the projection without confirmation. This may take some time! * @param bool $quiet If set only fatal errors are rendered to the output (must be used with --force flag to avoid user input) + * @param int $until Until which sequence number should projections be replayed? useful for debugging */ - public function projectionReplayAllCommand(string $contentRepository = 'default', bool $force = false, bool $quiet = false): void + public function projectionReplayAllCommand(string $contentRepository = 'default', bool $force = false, bool $quiet = false, int $until = 0): void { + if ($quiet) { + $this->output->getOutput()->setVerbosity(Output::VERBOSITY_QUIET); + } + $mainSection = ($this->output->getOutput() instanceof ConsoleOutput) ? $this->output->getOutput()->section() : $this->output->getOutput(); + $mainProgressBar = new ProgressBar($mainSection); + $mainProgressBar->setBarCharacter(''); + $mainProgressBar->setEmptyBarCharacter('░'); + $mainProgressBar->setProgressCharacter(''); + $mainProgressBar->setFormat('debug'); + + $subSection = ($this->output->getOutput() instanceof ConsoleOutput) ? $this->output->getOutput()->section() : $this->output->getOutput(); + $progressBar = new ProgressBar($subSection); + $progressBar->setFormat(' %message% - %current%/%max% [%bar%] %percent:3s%% %elapsed:16s%/%estimated:-16s% %memory:6s%'); if (!$force && $quiet) { $this->outputLine('Cannot run in quiet mode without --force. Please acknowledge that this command will reset and replay this projection. This may take some time.'); $this->quit(1); @@ -171,9 +191,28 @@ public function projectionReplayAllCommand(string $contentRepository = 'default' if (!$quiet) { $this->outputLine('Replaying events for all projections of Content Repository "%s" ...', [$contentRepositoryId->value]); } - // TODO progress bar with all events? Like projectionReplayCommand? - $projectionService->replayAllProjections(CatchUpOptions::create(), fn (string $projectionAlias) => $this->outputLine(sprintf(' * replaying %s projection', $projectionAlias))); + $options = CatchUpOptions::create(); + if (!$quiet) { + $options = $options->with(progressCallback: fn () => $progressBar->advance()); + } + if ($until > 0) { + $options = $options->with(maximumSequenceNumber: SequenceNumber::fromInteger($until)); + } + $highestSequenceNumber = max($until > 0 ? $until : $projectionService->highestSequenceNumber()->value, 1); + $mainProgressBar->start($projectionService->numberOfProjections()); + $mainProgressCallback = null; + if (!$quiet) { + $mainProgressCallback = static function (string $projectionAlias) use ($mainProgressBar, $progressBar, $highestSequenceNumber) { + $mainProgressBar->advance(); + $progressBar->setMessage($projectionAlias); + $progressBar->start($highestSequenceNumber); + $progressBar->setProgress(0); + }; + } + $projectionService->replayAllProjections($options, $mainProgressCallback); if (!$quiet) { + $mainProgressBar->finish(); + $progressBar->finish(); $this->outputLine('Done.'); } } diff --git a/Neos.ContentRepositoryRegistry/Classes/Service/ProjectionReplayService.php b/Neos.ContentRepositoryRegistry/Classes/Service/ProjectionReplayService.php index 2dcf16f96cc..00a946be01a 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Service/ProjectionReplayService.php +++ b/Neos.ContentRepositoryRegistry/Classes/Service/ProjectionReplayService.php @@ -9,6 +9,9 @@ use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\Projections; use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; +use Neos\EventStore\EventStoreInterface; +use Neos\EventStore\Model\Event\SequenceNumber; +use Neos\EventStore\Model\EventStream\VirtualStreamName; /** * Content Repository service to perform Projection replays @@ -21,6 +24,7 @@ final class ProjectionReplayService implements ContentRepositoryServiceInterface public function __construct( private readonly Projections $projections, private readonly ContentRepository $contentRepository, + private readonly EventStoreInterface $eventStore, ) { } @@ -49,6 +53,19 @@ public function resetAllProjections(): void } } + public function highestSequenceNumber(): SequenceNumber + { + foreach ($this->eventStore->load(VirtualStreamName::all())->backwards()->limit(1) as $eventEnvelope) { + return $eventEnvelope->sequenceNumber; + } + return SequenceNumber::none(); + } + + public function numberOfProjections(): int + { + return count($this->projections); + } + /** * @return class-string> */ diff --git a/Neos.ContentRepositoryRegistry/Classes/Service/ProjectionReplayServiceFactory.php b/Neos.ContentRepositoryRegistry/Classes/Service/ProjectionReplayServiceFactory.php index 134d34b4f27..337297d9bb6 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Service/ProjectionReplayServiceFactory.php +++ b/Neos.ContentRepositoryRegistry/Classes/Service/ProjectionReplayServiceFactory.php @@ -24,6 +24,7 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor return new ProjectionReplayService( $serviceFactoryDependencies->projections, $serviceFactoryDependencies->contentRepository, + $serviceFactoryDependencies->eventStore, ); } } From c49653bf2cd304dfd5f7dbbbb28244f313644eb9 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Wed, 24 Apr 2024 17:31:53 +0000 Subject: [PATCH 071/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index 19492ad687a..e8e47902fd7 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-04-20 +The following reference was automatically generated from code on 2024-04-24 .. _`Neos Command Reference: NEOS.FLOW`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index fbbccbbc411..ba3e661d0f7 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-04-20 +This reference was automatically generated from code on 2024-04-24 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index c89cc375d34..fca954e1414 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-20 +This reference was automatically generated from code on 2024-04-24 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index 2585b8ae34d..c1578f5b98d 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-04-20 +This reference was automatically generated from code on 2024-04-24 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index feed90c2fba..419b5177738 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-20 +This reference was automatically generated from code on 2024-04-24 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index 169589fd0b3..a32d6fb5f2b 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-04-20 +This reference was automatically generated from code on 2024-04-24 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From 042f5603b201a5f3bdc7bcd95b056f022a431ffb Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Wed, 24 Apr 2024 22:15:28 +0200 Subject: [PATCH 072/214] Feature: Allow to enable Fusion caching for Behat tests --- .../Features/Bootstrap/FusionTrait.php | 12 ++++ .../Features/Fusion/ContentCache.feature | 66 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 Neos.Neos/Tests/Behavior/Features/Fusion/ContentCache.feature diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FusionTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FusionTrait.php index 21b8f5ba725..8326cf17dcb 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FusionTrait.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FusionTrait.php @@ -47,6 +47,8 @@ trait FusionTrait private ?\Throwable $lastRenderingException = null; + private $contentCacheEnabled = false; + /** * @template T of object * @param class-string $className @@ -63,6 +65,7 @@ public function setupFusionContext(): void $this->fusionGlobalContext = []; $this->fusionContext = []; $this->fusionCode = null; + $this->contentCacheEnabled = false; $this->renderingResult = null; } @@ -114,6 +117,14 @@ public function iHaveTheFollowingFusionSetup(PyStringNode $fusionCode): void $this->fusionCode = $fusionCode->getRaw(); } + /** + * @When I have Fusion content cache enabled + */ + public function iHaveFusionContentCacheEnabled(): void + { + $this->contentCacheEnabled = true; + } + /** * @When I execute the following Fusion code: * @When I execute the following Fusion code on path :path: @@ -131,6 +142,7 @@ public function iExecuteTheFollowingFusionCode(PyStringNode $fusionCode, string $fusionGlobals = FusionGlobals::fromArray($this->fusionGlobalContext); $fusionRuntime = (new RuntimeFactory())->createFromConfiguration($fusionAst, $fusionGlobals); + $fusionRuntime->setEnableContentCache($this->contentCacheEnabled); $fusionRuntime->overrideExceptionHandler($this->getObject(ThrowingHandler::class)); $fusionRuntime->pushContextArray($this->fusionContext); try { diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCache.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCache.feature new file mode 100644 index 00000000000..b0e19d8f385 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCache.feature @@ -0,0 +1,66 @@ +@flowEntities @contentrepository +Feature: Tests for Fusion ContentCache + Background: + Given I have Fusion content cache enabled + And I have the following Fusion setup: + """fusion + include: resource://Neos.Fusion/Private/Fusion/Root.fusion + include: resource://Neos.Neos/Private/Fusion/Root.fusion + + prototype(Neos.Neos:Test.ContentCache) < prototype(Neos.Fusion:Component) { + foo = '' + renderer = ${props.foo} + @cache { + mode = 'cached' + entryIdentifier { + test = 'test' + } + } + } + +""" + + Scenario: + When I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.ContentCache { + foo = 'test1' + } + """ + Then I expect the following Fusion rendering result: + """ + test1 + """ + When I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.ContentCache { + foo = 'test2' + } + """ + Then I expect the following Fusion rendering result: + """ + test1 + """ + + + Scenario: + When I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.ContentCache { + foo = 'test2' + } + """ + Then I expect the following Fusion rendering result: + """ + test2 + """ + When I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.ContentCache { + foo = 'test1' + } + """ + Then I expect the following Fusion rendering result: + """ + test2 + """ \ No newline at end of file From 50b3bc613f253a09859549691f6ac00e9664b1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Jedenastik?= Date: Thu, 25 Apr 2024 14:19:33 +0200 Subject: [PATCH 073/214] !!! TASK: FusionView::assign has strict types --- Neos.Neos/Classes/View/FusionView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.Neos/Classes/View/FusionView.php b/Neos.Neos/Classes/View/FusionView.php index 8c21bf35eca..0783275afb1 100644 --- a/Neos.Neos/Classes/View/FusionView.php +++ b/Neos.Neos/Classes/View/FusionView.php @@ -229,7 +229,7 @@ protected function getFusionRuntime(Node $currentSiteNode) * @param string $key * @param mixed $value */ - public function assign($key, $value): AbstractView + public function assign(string $key, mixed $value): self { if ($key === 'request') { // the request cannot be used as "normal" fusion variable and must be treated as FusionGlobal From d7b617f40d9aed80ad4b401bcdc3af8d6ce19be6 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Thu, 25 Apr 2024 13:32:30 +0000 Subject: [PATCH 074/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index e8e47902fd7..2001cefc99c 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-04-24 +The following reference was automatically generated from code on 2024-04-25 .. _`Neos Command Reference: NEOS.FLOW`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index ba3e661d0f7..b5099e40660 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-04-24 +This reference was automatically generated from code on 2024-04-25 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index fca954e1414..76ae6baa662 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-24 +This reference was automatically generated from code on 2024-04-25 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index c1578f5b98d..6dfff4d7cb1 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-04-24 +This reference was automatically generated from code on 2024-04-25 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index 419b5177738..e624fcc36b7 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-24 +This reference was automatically generated from code on 2024-04-25 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index a32d6fb5f2b..fdaa4a2c5d9 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-04-24 +This reference was automatically generated from code on 2024-04-25 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From 23ebd0c54474d3c72b166c24d50c6c534ff09515 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Thu, 25 Apr 2024 21:17:08 +0200 Subject: [PATCH 075/214] Feature: Allow to enable Fusion caching for Behat tests --- .../Features/Bootstrap/FusionTrait.php | 9 +++++++++ .../Features/Fusion/ContentCache.feature | 20 +++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FusionTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FusionTrait.php index 8326cf17dcb..b1ebbbd35a5 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FusionTrait.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FusionTrait.php @@ -28,6 +28,7 @@ use Neos\Neos\Domain\Service\RenderingModeService; use PHPUnit\Framework\Assert; use Psr\Http\Message\ServerRequestFactoryInterface; +use Neos\Fusion\Core\Cache\ContentCache; /** * @internal only for behat tests within the Neos.Neos package @@ -204,4 +205,12 @@ public function throwExceptionIfLastRenderingLedToAnError(): void } } + /** + * @BeforeScenario + */ + public function clearFusionCaches() + { + $this->getObject(ContentCache::class)->flush(); + } + } diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCache.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCache.feature index b0e19d8f385..bc0c4c68455 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCache.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCache.feature @@ -20,47 +20,47 @@ Feature: Tests for Fusion ContentCache """ - Scenario: + Scenario: Render a cached prototype and check if rerendering doesn't happen on second try When I execute the following Fusion code: """fusion test = Neos.Neos:Test.ContentCache { - foo = 'test1' + foo = 'some-cached-string' } """ Then I expect the following Fusion rendering result: """ - test1 + some-cached-string """ When I execute the following Fusion code: """fusion test = Neos.Neos:Test.ContentCache { - foo = 'test2' + foo = 'some-other-string' } """ Then I expect the following Fusion rendering result: """ - test1 + some-cached-string """ - Scenario: + Scenario: Check if cached got flushed before running a new scenario and no leftover of last test is there When I execute the following Fusion code: """fusion test = Neos.Neos:Test.ContentCache { - foo = 'test2' + foo = 'some-new-string' } """ Then I expect the following Fusion rendering result: """ - test2 + some-new-string """ When I execute the following Fusion code: """fusion test = Neos.Neos:Test.ContentCache { - foo = 'test1' + foo = 'totally-different-string' } """ Then I expect the following Fusion rendering result: """ - test2 + some-new-string """ \ No newline at end of file From ebc0ddfe89edb23a538f779aa577b9d32dec0aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Wed, 6 Mar 2024 19:41:46 +0100 Subject: [PATCH 076/214] TASK: Remove "internal" node properties Provides a helper to access hidden status as well as renaming `_hiddenInIndex` to `hiddenInMenu`. Fixes: #4208 --- .../AbstractMenuItemsImplementation.php | 22 +++++++++---------- .../Classes/Fusion/Helper/NodeHelper.php | 1 + .../References/NeosFusionReference.rst | 12 +++++----- Neos.Neos/NodeTypes/Mixin/Document.yaml | 2 +- .../Behavior/Features/Fusion/Menu.feature | 4 ++-- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php index 23f8055dc8a..18c0ded20f3 100644 --- a/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php @@ -24,7 +24,7 @@ * Base class for Menu and DimensionsMenu * * Main Options: - * - renderHiddenInIndex: if TRUE, hidden-in-index nodes will be shown in the menu. FALSE by default. + * - renderHiddenInMenu: if TRUE, hidden-in-index nodes will be shown in the menu. FALSE by default. */ abstract class AbstractMenuItemsImplementation extends AbstractFusionObject { @@ -46,11 +46,11 @@ abstract class AbstractMenuItemsImplementation extends AbstractFusionObject protected $currentNode; /** - * Internal cache for the renderHiddenInIndex property. + * Internal cache for the renderHiddenInMenu property. * * @var boolean */ - protected $renderHiddenInIndex; + protected $renderHiddenInMenu; /** * Internal cache for the calculateItemStates property. @@ -76,17 +76,17 @@ public function isCalculateItemStatesEnabled(): bool } /** - * Should nodes that have "hiddenInIndex" set still be visible in this menu. + * Should nodes that have "hiddenInMenu" set still be visible in this menu. * * @return boolean */ - public function getRenderHiddenInIndex() + public function getRenderHiddenInMenu() { - if ($this->renderHiddenInIndex === null) { - $this->renderHiddenInIndex = (bool)$this->fusionValue('renderHiddenInIndex'); + if ($this->renderHiddenInMenu === null) { + $this->renderHiddenInMenu = (bool)($this->fusionValue('renderHiddenInMenu') ?? $this->fusionValue('renderHiddenInIndex')); } - return $this->renderHiddenInIndex; + return $this->renderHiddenInMenu; } /** @@ -139,7 +139,7 @@ abstract protected function buildItems(): array; /** * Return TRUE/FALSE if the node is currently hidden or not in the menu; - * taking the "renderHiddenInIndex" configuration of the Menu Fusion object into account. + * taking the "renderHiddenInMenu" configuration of the Menu Fusion object into account. * * This method needs to be called inside buildItems() in the subclasses. * @@ -148,14 +148,14 @@ abstract protected function buildItems(): array; */ protected function isNodeHidden(Node $node) { - if ($this->getRenderHiddenInIndex() === true) { + if ($this->getRenderHiddenInMenu() === true) { // Please show hiddenInIndex nodes // -> node is *never* hidden! return false; } // Node is hidden depending on the _hiddenInIndex property - return $node->getProperty('_hiddenInIndex'); + return $node->getProperty('hiddenInMenu'); } protected function buildUri(Node $node): string diff --git a/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php b/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php index 28de5534bb7..8bbdd02f71b 100644 --- a/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php @@ -21,6 +21,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindAncestorNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; +use Neos\ContentRepository\Core\Projection\NodeHiddenState\NodeHiddenStateFinder; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Eel\ProtectedContextAwareInterface; use Neos\Flow\Annotations as Flow; diff --git a/Neos.Neos/Documentation/References/NeosFusionReference.rst b/Neos.Neos/Documentation/References/NeosFusionReference.rst index 113099e9115..1e7967244ac 100644 --- a/Neos.Neos/Documentation/References/NeosFusionReference.rst +++ b/Neos.Neos/Documentation/References/NeosFusionReference.rst @@ -927,7 +927,7 @@ The following properties are passed over to :ref:`Neos_Neos__MenuItems` internal :maximumLevels: (integer) Restrict the maximum depth of items in the menu (relative to ``entryLevel``) :startingPoint: (optional, Node) The node where the menu hierarchy starts. If not specified explicitly the startingPoint is calculated from (``node`` and ``entryLevel``), defaults to ``null`` :filter: (string) Filter items by node type (e.g. ``'!My.Site:News,Neos.Neos:Document'``), defaults to ``'Neos.Neos:Document'``. The filter is only used for fetching subItems and is ignored for determining the ``startingPoint`` -:renderHiddenInIndex: (boolean) Whether nodes with ``hiddenInIndex`` should be rendered, defaults to ``false`` +:renderHiddenInMenu: (boolean) Whether nodes with ``hiddenInIndex`` should be rendered, defaults to ``false`` :calculateItemStates: (boolean) activate the *expensive* calculation of item states defaults to ``false``. :itemCollection: (optional, array of Nodes) Explicitly set the Node items for the menu (taking precedence over ``startingPoints`` and ``entryLevel`` and ``lastLevel``). The children for each ``Node`` will be fetched taking the ``maximumLevels`` property into account. @@ -957,7 +957,7 @@ The following properties are passed over to :ref:`Neos_Neos__BreadcrumbMenuItems :node: (Node) The current node to render the menu for. Defaults to ``documentNode`` from the fusion context :maximumLevels: (integer) Restrict the maximum depth of items in the menu, defaults to ``0`` -:renderHiddenInIndex: (boolean) Whether nodes with ``hiddenInIndex`` should be rendered (the current documentNode is always included), defaults to ``false``. +:renderHiddenInMenu: (boolean) Whether nodes with ``hiddenInIndex`` should be rendered (the current documentNode is always included), defaults to ``false``. :calculateItemStates: (boolean) activate the *expensive* calculation of item states defaults to ``false`` Example:: @@ -986,7 +986,7 @@ The following fusion properties are passed over to :ref:`Neos_Neos__DimensionsMe :dimension: (optional, string): name of the dimension which this menu should be based on. Example: "language". :presets: (optional, array): If set, the presets rendered will be taken from this list of preset identifiers :includeAllPresets: (boolean, default **false**) If TRUE, include all presets, not only allowed combinations -:renderHiddenInIndex: (boolean, default **true**) If TRUE, render nodes which are marked as "hidded-in-index" +:renderHiddenInMenu: (boolean, default **true**) If TRUE, render nodes which are marked as "hidded-in-index" :calculateItemStates: (boolean) activate the *expensive* calculation of item states defaults to ``false`` .. note:: The ``items`` of the ``DimensionsMenu`` are internally calculated with the prototype :ref:`Neos_Neos__DimensionsMenuItems` which @@ -1017,7 +1017,7 @@ Create a list of menu-items items for nodes. :maximumLevels: (integer) Restrict the maximum depth of items in the menu (relative to ``entryLevel``) :startingPoint: (optional, Node) The node where the menu hierarchy starts. If not specified explicitly the startingPoint is calculated from (``node`` and ``entryLevel``), defaults to ``null`` :filter: (string) Filter items by node type (e.g. ``'!My.Site:News,Neos.Neos:Document'``), defaults to ``'Neos.Neos:Document'``. The filter is only used for fetching subItems and is ignored for determining the ``startingPoint`` -:renderHiddenInIndex: (boolean) Whether nodes with ``hiddenInIndex`` should be rendered, defaults to ``false`` +:renderHiddenInMenu: (boolean) Whether nodes with ``hiddenInIndex`` should be rendered, defaults to ``false`` :calculateItemStates: (boolean) activate the *expensive* calculation of item states defaults to ``false``. :itemCollection: (optional, array of Nodes) Explicitly set the Node items for the menu (taking precedence over ``startingPoints`` and ``entryLevel`` and ``lastLevel``). The children for each ``Node`` will be fetched taking the ``maximumLevels`` property into account. @@ -1084,7 +1084,7 @@ Create a list of of menu-items for the breadcrumb (ancestor documents). :node: (Node) The current node to render the menu for. Defaults to ``documentNode`` from the fusion context :maximumLevels: (integer) Restrict the maximum depth of items in the menu, defaults to ``0`` -:renderHiddenInIndex: (boolean) Whether nodes with ``hiddenInIndex`` should be rendered (the current documentNode is always included), defaults to ``false``. +:renderHiddenInMenu: (boolean) Whether nodes with ``hiddenInIndex`` should be rendered (the current documentNode is always included), defaults to ``false``. :calculateItemStates: (boolean) activate the *expensive* calculation of item states defaults to ``false`` Example:: @@ -1107,7 +1107,7 @@ If no node variant exists for the preset combination, a ``NULL`` node will be in :dimension: (optional, string): name of the dimension which this menu should be based on. Example: "language". :presets: (optional, array): If set, the presets rendered will be taken from this list of preset identifiers :includeAllPresets: (boolean, default **false**) If TRUE, include all presets, not only allowed combinations -:renderHiddenInIndex: (boolean, default **true**) If TRUE, render nodes which are marked as "hidded-in-index" +:renderHiddenInMenu: (boolean, default **true**) If TRUE, render nodes which are marked as "hidded-in-index" :calculateItemStates: (boolean) activate the *expensive* calculation of item states defaults to ``false`` Each ``item`` has the following properties: diff --git a/Neos.Neos/NodeTypes/Mixin/Document.yaml b/Neos.Neos/NodeTypes/Mixin/Document.yaml index 6fdfbaf2eda..c3277bf4344 100644 --- a/Neos.Neos/NodeTypes/Mixin/Document.yaml +++ b/Neos.Neos/NodeTypes/Mixin/Document.yaml @@ -59,7 +59,7 @@ _hidden: ui: reloadPageIfChanged: true - _hiddenInIndex: + hiddenInMenu: type: boolean ui: label: i18n diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature index d137b1b77ec..73cbcbec51c 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature @@ -15,7 +15,7 @@ Feature: Tests for the "Neos.Neos:Menu" and related Fusion prototypes type: string uriPathSegment: type: string - _hiddenInIndex: + hiddenInIndex: type: bool 'Neos.Neos:Site': superTypes: @@ -63,7 +63,7 @@ Feature: Tests for the "Neos.Neos:Menu" and related Fusion prototypes | a1b1b | a1b1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1b1b", "title": "Node a1b1b"} | a1b1b | | a1b2 | a1b | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a1b2", "title": "Node a1b2"} | a1b2 | | a1b3 | a1b | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1b3", "title": "Node a1b3"} | a1b3 | - | a1c | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1c", "title": "Node a1c", "_hiddenInIndex": true} | a1c | + | a1c | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1c", "title": "Node a1c", "hiddenInIndex": true} | a1c | | a1c1 | a1c | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1c1", "title": "Node a1c1"} | a1c1 | And A site exists for node name "a" and domain "http://localhost" And the sites configuration is: From f9808e26b623638d0ace49957260793a4f7c2034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Thu, 7 Mar 2024 09:36:58 +0100 Subject: [PATCH 077/214] Behat fix for menu rendering with hiddenInMenu --- Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature index 73cbcbec51c..541e1c5444f 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature @@ -15,7 +15,7 @@ Feature: Tests for the "Neos.Neos:Menu" and related Fusion prototypes type: string uriPathSegment: type: string - hiddenInIndex: + hiddenInMenu: type: bool 'Neos.Neos:Site': superTypes: @@ -63,7 +63,7 @@ Feature: Tests for the "Neos.Neos:Menu" and related Fusion prototypes | a1b1b | a1b1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1b1b", "title": "Node a1b1b"} | a1b1b | | a1b2 | a1b | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a1b2", "title": "Node a1b2"} | a1b2 | | a1b3 | a1b | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1b3", "title": "Node a1b3"} | a1b3 | - | a1c | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1c", "title": "Node a1c", "hiddenInIndex": true} | a1c | + | a1c | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1c", "title": "Node a1c", "hiddenInMenu": true} | a1c | | a1c1 | a1c | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1c1", "title": "Node a1c1"} | a1c1 | And A site exists for node name "a" and domain "http://localhost" And the sites configuration is: @@ -352,12 +352,12 @@ Feature: Tests for the "Neos.Neos:Menu" and related Fusion prototypes """ - Scenario: MenuItems (renderHiddenInIndex) + Scenario: MenuItems (renderHiddenInMenu) When I execute the following Fusion code: """fusion test = Neos.Neos:Test.Menu { items = Neos.Neos:MenuItems { - renderHiddenInIndex = true + renderHiddenInMenu = true } } """ From 565bff10986f6df249e5f80daea99806b2ac8590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Mon, 11 Mar 2024 12:49:15 +0100 Subject: [PATCH 078/214] Adjust legacy migration and labels to hiddenInMenu --- .../Classes/NodeDataToEventsProcessor.php | 2 +- .../Classes/FlowQueryOperations/FilterOperation.php | 2 +- .../Classes/Fusion/AbstractMenuItemsImplementation.php | 4 ++-- .../Private/Translations/ar/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/cs/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/da/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/de/NodeTypes/Document.xlf | 2 +- .../Private/Translations/el/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/en/NodeTypes/Document.xlf | 2 +- .../Private/Translations/es/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/fi/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/fr/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/hu/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/id_ID/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/it/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/ja/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/km/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/lv/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/nl/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/no/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/pl/NodeTypes/Document.xlf | 6 +++--- .../Private/Translations/pt/NodeTypes/Document.xlf | 2 +- .../Private/Translations/pt_BR/NodeTypes/Document.xlf | 4 ++-- .../Private/Translations/ru/NodeTypes/Document.xlf | 4 ++-- .../Private/Translations/sr/NodeTypes/Document.xlf | 4 ++-- .../Private/Translations/sv/NodeTypes/Document.xlf | 4 ++-- .../Private/Translations/tl_PH/NodeTypes/Document.xlf | 4 ++-- .../Private/Translations/tr/NodeTypes/Document.xlf | 4 ++-- .../Private/Translations/uk/NodeTypes/Document.xlf | 4 ++-- .../Private/Translations/vi/NodeTypes/Document.xlf | 4 ++-- .../Private/Translations/zh/NodeTypes/Document.xlf | 4 ++-- .../Private/Translations/zh_TW/NodeTypes/Document.xlf | 4 ++-- Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature | 4 ++-- 33 files changed, 77 insertions(+), 77 deletions(-) diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php index a621f5309e2..cfe8e1a8dea 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php @@ -378,7 +378,7 @@ public function extractPropertyValuesAndReferences(array $nodeDataRow, NodeType // hiddenInIndex is stored as separate column in the nodedata table, but we need it as (internal) property if ($nodeDataRow['hiddeninindex']) { - $properties['_hiddenInIndex'] = true; + $properties['hiddenInMenu'] = true; } if ($nodeType->isOfType(NodeTypeName::fromString('Neos.TimeableNodeVisibility:Timeable'))) { diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FilterOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FilterOperation.php index 97d586a8e92..ea12e20c0b1 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FilterOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FilterOperation.php @@ -122,7 +122,7 @@ protected function getPropertyPath($element, $propertyPath) if ($propertyPath === '_identifier') { // TODO: deprecated (Neos <9 case) return $element->nodeAggregateId->value; - } elseif ($propertyPath[0] === '_' && $propertyPath !== '_hiddenInIndex') { + } elseif ($propertyPath[0] === '_') { return ObjectAccess::getPropertyPath($element, substr($propertyPath, 1)); } else { return $element->getProperty($propertyPath); diff --git a/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php index 18c0ded20f3..a30e63316f0 100644 --- a/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php @@ -149,12 +149,12 @@ abstract protected function buildItems(): array; protected function isNodeHidden(Node $node) { if ($this->getRenderHiddenInMenu() === true) { - // Please show hiddenInIndex nodes + // Please show hiddenInMenu nodes // -> node is *never* hidden! return false; } - // Node is hidden depending on the _hiddenInIndex property + // Node is hidden depending on the hiddenInMenu property return $node->getProperty('hiddenInMenu'); } diff --git a/Neos.Neos/Resources/Private/Translations/ar/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/ar/NodeTypes/Document.xlf index 74a8a479afe..d7c188cdda8 100644 --- a/Neos.Neos/Resources/Private/Translations/ar/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/ar/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment جزء من مسار URL - - Hide in menus - إخفاء في القوائم + + Hide in menus + إخفاء في القوائم diff --git a/Neos.Neos/Resources/Private/Translations/cs/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/cs/NodeTypes/Document.xlf index 4c0c4b4d23e..0bd687cc256 100644 --- a/Neos.Neos/Resources/Private/Translations/cs/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/cs/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment Segment cesty adresy URL - - Hide in menus - Skrýt v menu + + Hide in menus + Skrýt v menu diff --git a/Neos.Neos/Resources/Private/Translations/da/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/da/NodeTypes/Document.xlf index 88eb66041b2..8269a2f251b 100644 --- a/Neos.Neos/Resources/Private/Translations/da/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/da/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment URL adressesegment - - Hide in menus - Skjul i menuer + + Hide in menus + Skjul i menuer diff --git a/Neos.Neos/Resources/Private/Translations/de/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/de/NodeTypes/Document.xlf index 33f56f2d736..39d815b8867 100644 --- a/Neos.Neos/Resources/Private/Translations/de/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/de/NodeTypes/Document.xlf @@ -11,7 +11,7 @@ URL path segment URL Pfadsegment - + Hide in menus In Menüs verbergen diff --git a/Neos.Neos/Resources/Private/Translations/el/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/el/NodeTypes/Document.xlf index f84c70cb9e9..aab2340d833 100644 --- a/Neos.Neos/Resources/Private/Translations/el/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/el/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment URL path segment - - Hide in menus - Κρυφό στα μενού + + Hide in menus + Κρυφό στα μενού diff --git a/Neos.Neos/Resources/Private/Translations/en/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/en/NodeTypes/Document.xlf index 00d11fc2e6e..8469025151e 100644 --- a/Neos.Neos/Resources/Private/Translations/en/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/en/NodeTypes/Document.xlf @@ -11,7 +11,7 @@ URL path segment - + Hide in menus diff --git a/Neos.Neos/Resources/Private/Translations/es/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/es/NodeTypes/Document.xlf index 6e5c98088c3..6dc1a90da53 100644 --- a/Neos.Neos/Resources/Private/Translations/es/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/es/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment Segmento de ruta de URL - - Hide in menus - Ocultar en los menús + + Hide in menus + Ocultar en los menús diff --git a/Neos.Neos/Resources/Private/Translations/fi/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/fi/NodeTypes/Document.xlf index 37391b8c509..a90b3c282c6 100644 --- a/Neos.Neos/Resources/Private/Translations/fi/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/fi/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment URL-polun osa - - Hide in menus - Piilota valikoissa + + Hide in menus + Piilota valikoissa diff --git a/Neos.Neos/Resources/Private/Translations/fr/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/fr/NodeTypes/Document.xlf index 1b983a0be8d..fca65146bd8 100644 --- a/Neos.Neos/Resources/Private/Translations/fr/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/fr/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment Segment d'URL - - Hide in menus - Cacher dans les menus + + Hide in menus + Cacher dans les menus diff --git a/Neos.Neos/Resources/Private/Translations/hu/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/hu/NodeTypes/Document.xlf index 5b45270dfd8..da3b51d122f 100644 --- a/Neos.Neos/Resources/Private/Translations/hu/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/hu/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment URL szegmens - - Hide in menus - Elrejtés a menüben + + Hide in menus + Elrejtés a menüben diff --git a/Neos.Neos/Resources/Private/Translations/id_ID/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/id_ID/NodeTypes/Document.xlf index 339798d9413..e87286032d2 100644 --- a/Neos.Neos/Resources/Private/Translations/id_ID/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/id_ID/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment Segmen path URL - - Hide in menus - Sembunyikan di Menu + + Hide in menus + Sembunyikan di Menu diff --git a/Neos.Neos/Resources/Private/Translations/it/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/it/NodeTypes/Document.xlf index 4887f24b0f0..9908e9b9731 100644 --- a/Neos.Neos/Resources/Private/Translations/it/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/it/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment Segmento di percorso URL - - Hide in menus - Nascondi nei menu + + Hide in menus + Nascondi nei menu diff --git a/Neos.Neos/Resources/Private/Translations/ja/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/ja/NodeTypes/Document.xlf index cb9aaef87e7..64e6babf5c3 100644 --- a/Neos.Neos/Resources/Private/Translations/ja/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/ja/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment URL パスセグメント - - Hide in menus - メニュー非表示 + + Hide in menus + メニュー非表示 diff --git a/Neos.Neos/Resources/Private/Translations/km/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/km/NodeTypes/Document.xlf index cbf91093ce3..caf5a07c412 100644 --- a/Neos.Neos/Resources/Private/Translations/km/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/km/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment បំណែកផ្លូវនៃតំណ - - Hide in menus - មិនបង្ហាុញក្នុងមីនុយ + + Hide in menus + មិនបង្ហាុញក្នុងមីនុយ diff --git a/Neos.Neos/Resources/Private/Translations/lv/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/lv/NodeTypes/Document.xlf index 1633085c3fa..bc4d85b6e30 100644 --- a/Neos.Neos/Resources/Private/Translations/lv/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/lv/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment URL segments - - Hide in menus - Paslēpt izvēlnē + + Hide in menus + Paslēpt izvēlnē diff --git a/Neos.Neos/Resources/Private/Translations/nl/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/nl/NodeTypes/Document.xlf index 6be62d17124..ef3a840a539 100644 --- a/Neos.Neos/Resources/Private/Translations/nl/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/nl/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment URL pad segment - - Hide in menus - Verberg in menu's + + Hide in menus + Verberg in menu's diff --git a/Neos.Neos/Resources/Private/Translations/no/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/no/NodeTypes/Document.xlf index 8205579ac5c..b61861affb8 100644 --- a/Neos.Neos/Resources/Private/Translations/no/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/no/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment URL-banesegment - - Hide in menus - Skjul i menyer + + Hide in menus + Skjul i menyer diff --git a/Neos.Neos/Resources/Private/Translations/pl/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/pl/NodeTypes/Document.xlf index fc17daa0889..9739b067486 100644 --- a/Neos.Neos/Resources/Private/Translations/pl/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/pl/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment Segment ścieżki URL - - Hide in menus - Ukryj w menu + + Hide in menus + Ukryj w menu diff --git a/Neos.Neos/Resources/Private/Translations/pt/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/pt/NodeTypes/Document.xlf index 310986d4a13..c69b2e09a2f 100644 --- a/Neos.Neos/Resources/Private/Translations/pt/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/pt/NodeTypes/Document.xlf @@ -14,7 +14,7 @@ URL path segment Segmento de caminho de URL - + Hide in menus Ocultar em menus diff --git a/Neos.Neos/Resources/Private/Translations/pt_BR/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/pt_BR/NodeTypes/Document.xlf index af3854dfa6f..51de93f4a7d 100644 --- a/Neos.Neos/Resources/Private/Translations/pt_BR/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/pt_BR/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment Segmento de URL - + Hide in menus - Esconder em menus + Esconder em menus diff --git a/Neos.Neos/Resources/Private/Translations/ru/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/ru/NodeTypes/Document.xlf index d8e771abbe4..70ca1a9bbf5 100644 --- a/Neos.Neos/Resources/Private/Translations/ru/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/ru/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment Сегмент пути URL-адреса - + Hide in menus - Скрыть в меню + Скрыть в меню diff --git a/Neos.Neos/Resources/Private/Translations/sr/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/sr/NodeTypes/Document.xlf index 4561f58bf69..b61c828dfc0 100644 --- a/Neos.Neos/Resources/Private/Translations/sr/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/sr/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment Сегмент URL путање - + Hide in menus - Сакривено у менију + Сакривено у менију diff --git a/Neos.Neos/Resources/Private/Translations/sv/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/sv/NodeTypes/Document.xlf index 8c710d59a77..e5d4c4476fd 100644 --- a/Neos.Neos/Resources/Private/Translations/sv/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/sv/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment URL-sökvägssegment - + Hide in menus - Dölj i menyer + Dölj i menyer diff --git a/Neos.Neos/Resources/Private/Translations/tl_PH/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/tl_PH/NodeTypes/Document.xlf index 3b426ba0275..7dda1678a82 100644 --- a/Neos.Neos/Resources/Private/Translations/tl_PH/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/tl_PH/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment URL path segment - + Hide in menus - Hide in menus + Hide in menus diff --git a/Neos.Neos/Resources/Private/Translations/tr/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/tr/NodeTypes/Document.xlf index 10be0efa791..677ff9ab331 100644 --- a/Neos.Neos/Resources/Private/Translations/tr/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/tr/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment URL yolu segmenti - + Hide in menus - Menülerde gizle + Menülerde gizle diff --git a/Neos.Neos/Resources/Private/Translations/uk/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/uk/NodeTypes/Document.xlf index a2918f802ac..d1c6cd0c742 100644 --- a/Neos.Neos/Resources/Private/Translations/uk/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/uk/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment Сегмент адреси URL - + Hide in menus - Сховати до меню + Сховати до меню diff --git a/Neos.Neos/Resources/Private/Translations/vi/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/vi/NodeTypes/Document.xlf index 4700ec83d27..3781cc18f57 100644 --- a/Neos.Neos/Resources/Private/Translations/vi/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/vi/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment Phân đoạn đường dẫn URL - + Hide in menus - Ẩn trong menu + Ẩn trong menu diff --git a/Neos.Neos/Resources/Private/Translations/zh/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/zh/NodeTypes/Document.xlf index debc5db04b4..8b8b80080d1 100644 --- a/Neos.Neos/Resources/Private/Translations/zh/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/zh/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment URL 路径段 - + Hide in menus - 在菜单中隐藏 + 在菜单中隐藏 diff --git a/Neos.Neos/Resources/Private/Translations/zh_TW/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/zh_TW/NodeTypes/Document.xlf index 42ff61d4db0..4189a772f39 100644 --- a/Neos.Neos/Resources/Private/Translations/zh_TW/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/zh_TW/NodeTypes/Document.xlf @@ -14,9 +14,9 @@ URL path segment 網址路徑 - + Hide in menus - 於選單中隱藏 + 於選單中隱藏 diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature index b7073940274..f21aa288f68 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature @@ -15,7 +15,7 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. type: string uriPathSegment: type: string - _hiddenInIndex: + hiddenInMenu: type: bool 'Neos.Neos:Site': superTypes: @@ -71,7 +71,7 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. | a1b1b | a1b1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1b1b", "title": "Node a1b1b"} | a1b1b | | a1b2 | a1b | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a1b2", "title": "Node a1b2"} | a1b2 | | a1b3 | a1b | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1b3", "title": "Node a1b3"} | a1b3 | - | a1c | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1c", "title": "Node a1c", "_hiddenInIndex": true} | a1c | + | a1c | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1c", "title": "Node a1c", "hiddenInMenu": true} | a1c | | a1c1 | a1c | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1c1", "title": "Node a1c1"} | a1c1 | And A site exists for node name "a" and domain "http://localhost" And the sites configuration is: From 621284145e473b3a7ffa17dd14794c64f7bf17e4 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 26 Apr 2024 11:07:33 +0000 Subject: [PATCH 079/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index 2001cefc99c..85fa36e4b04 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-04-25 +The following reference was automatically generated from code on 2024-04-26 .. _`Neos Command Reference: NEOS.FLOW`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index b5099e40660..d5aa331efdf 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-04-25 +This reference was automatically generated from code on 2024-04-26 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index 76ae6baa662..cfbd2e01516 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-25 +This reference was automatically generated from code on 2024-04-26 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index 6dfff4d7cb1..e549a156d77 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-04-25 +This reference was automatically generated from code on 2024-04-26 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index e624fcc36b7..621f9975b1e 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-25 +This reference was automatically generated from code on 2024-04-26 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index fdaa4a2c5d9..9dcac440621 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-04-25 +This reference was automatically generated from code on 2024-04-26 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From 4dcf5dc80ddd9c4d4d3c0593d36c25e96f202c80 Mon Sep 17 00:00:00 2001 From: xLaura3m5 Date: Fri, 26 Apr 2024 17:04:00 +0200 Subject: [PATCH 080/214] TASK: set new-password in EditAccount page --- .../Resources/Private/Partials/Module/Shared/EditAccount.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neos.Neos/Resources/Private/Partials/Module/Shared/EditAccount.html b/Neos.Neos/Resources/Private/Partials/Module/Shared/EditAccount.html index 2c3c9c0a45e..2c690c78077 100644 --- a/Neos.Neos/Resources/Private/Partials/Module/Shared/EditAccount.html +++ b/Neos.Neos/Resources/Private/Partials/Module/Shared/EditAccount.html @@ -13,14 +13,14 @@
- +
- +
From e8138ba0bd29b9bc53a2134843e3af5271899a98 Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Fri, 26 Apr 2024 17:09:32 +0200 Subject: [PATCH 081/214] FEATURE: Cleanup `EventNormalizer` Add `EventNormalizer::normalize()` and make `getEventData()` and `getEventType()` private --- .../Classes/EventStore/EventNormalizer.php | 70 ++++++++++++------- .../Classes/EventStore/EventPersister.php | 29 ++------ .../Processors/EventStoreImportProcessor.php | 40 +---------- .../Classes/NodeDataToEventsProcessor.php | 9 +-- 4 files changed, 58 insertions(+), 90 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/EventStore/EventNormalizer.php b/Neos.ContentRepository.Core/Classes/EventStore/EventNormalizer.php index bd25c64b498..f0bf2926419 100644 --- a/Neos.ContentRepository.Core/Classes/EventStore/EventNormalizer.php +++ b/Neos.ContentRepository.Core/Classes/EventStore/EventNormalizer.php @@ -43,6 +43,7 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\EventStore\Model\Event; use Neos\EventStore\Model\Event\EventData; +use Neos\EventStore\Model\Event\EventId; use Neos\EventStore\Model\Event\EventType; /** @@ -117,32 +118,6 @@ public function __construct() } } - public function getEventData(EventInterface $event): EventData - { - try { - $eventDataAsJson = json_encode($event, JSON_THROW_ON_ERROR); - } catch (\JsonException $exception) { - throw new \InvalidArgumentException( - sprintf( - 'Failed to normalize event of type "%s": %s', - get_debug_type($event), - $exception->getMessage() - ), - 1651838981 - ); - } - return EventData::fromString($eventDataAsJson); - } - - public function getEventType(EventInterface $event): EventType - { - $className = get_class($event); - - return $this->fullClassNameToShortEventType[$className] ?? throw new \RuntimeException( - 'Event type ' . get_class($event) . ' not registered' - ); - } - /** * @return class-string */ @@ -154,6 +129,23 @@ public function getEventClassName(Event $event): string ); } + public function normalize(EventInterface|DecoratedEvent $event): Event + { + $eventId = $event instanceof DecoratedEvent && $event->eventId !== null ? $event->eventId : EventId::create(); + $eventMetadata = $event instanceof DecoratedEvent ? $event->eventMetadata : null; + $causationId = $event instanceof DecoratedEvent ? $event->causationId : null; + $correlationId = $event instanceof DecoratedEvent ? $event->correlationId : null; + $event = $event instanceof DecoratedEvent ? $event->innerEvent : $event; + return new Event( + $eventId, + $this->getEventType($event), + $this->getEventData($event), + $eventMetadata, + $causationId, + $correlationId, + ); + } + public function denormalize(Event $event): EventInterface { $eventClassName = $this->getEventClassName($event); @@ -177,4 +169,30 @@ public function denormalize(Event $event): EventInterface default => $eventInstance, }; } + + private function getEventData(EventInterface $event): EventData + { + try { + $eventDataAsJson = json_encode($event, JSON_THROW_ON_ERROR); + } catch (\JsonException $exception) { + throw new \InvalidArgumentException( + sprintf( + 'Failed to normalize event of type "%s": %s', + get_debug_type($event), + $exception->getMessage() + ), + 1651838981 + ); + } + return EventData::fromString($eventDataAsJson); + } + + private function getEventType(EventInterface $event): EventType + { + $className = get_class($event); + + return $this->fullClassNameToShortEventType[$className] ?? throw new \RuntimeException( + 'Event type ' . get_class($event) . ' not registered' + ); + } } diff --git a/Neos.ContentRepository.Core/Classes/EventStore/EventPersister.php b/Neos.ContentRepository.Core/Classes/EventStore/EventPersister.php index 80b4aa07c94..4cc945eddcb 100644 --- a/Neos.ContentRepository.Core/Classes/EventStore/EventPersister.php +++ b/Neos.ContentRepository.Core/Classes/EventStore/EventPersister.php @@ -21,13 +21,13 @@ * * @internal */ -final class EventPersister +final readonly class EventPersister { public function __construct( - private readonly EventStoreInterface $eventStore, - private readonly ProjectionCatchUpTriggerInterface $projectionCatchUpTrigger, - private readonly EventNormalizer $eventNormalizer, - private readonly Projections $projections, + private EventStoreInterface $eventStore, + private ProjectionCatchUpTriggerInterface $projectionCatchUpTrigger, + private EventNormalizer $eventNormalizer, + private Projections $projections, ) { } @@ -44,7 +44,7 @@ public function publishEvents(EventsToPublish $eventsToPublish): CommandResult // the following logic could also be done in an AppEventStore::commit method (being called // directly from the individual Command Handlers). $normalizedEvents = Events::fromArray( - $eventsToPublish->events->map(fn(EventInterface|DecoratedEvent $event) => $this->normalizeEvent($event)) + $eventsToPublish->events->map($this->eventNormalizer->normalize(...)) ); $commitResult = $this->eventStore->commit( $eventsToPublish->streamName, @@ -70,21 +70,4 @@ public function publishEvents(EventsToPublish $eventsToPublish): CommandResult // The CommandResult can be used to block until projections are up to date. return new CommandResult($pendingProjections, $commitResult); } - - private function normalizeEvent(EventInterface|DecoratedEvent $event): Event - { - $eventId = $event instanceof DecoratedEvent && $event->eventId !== null ? $event->eventId : EventId::create(); - $eventMetadata = $event instanceof DecoratedEvent ? $event->eventMetadata : null; - $causationId = $event instanceof DecoratedEvent ? $event->causationId : null; - $correlationId = $event instanceof DecoratedEvent ? $event->correlationId : null; - $event = $event instanceof DecoratedEvent ? $event->innerEvent : $event; - return new Event( - $eventId, - $this->eventNormalizer->getEventType($event), - $this->eventNormalizer->getEventData($event), - $eventMetadata, - $causationId, - $correlationId, - ); - } } diff --git a/Neos.ContentRepository.Export/src/Processors/EventStoreImportProcessor.php b/Neos.ContentRepository.Export/src/Processors/EventStoreImportProcessor.php index 1efa5bd062f..80b18a9b3dc 100644 --- a/Neos.ContentRepository.Export/src/Processors/EventStoreImportProcessor.php +++ b/Neos.ContentRepository.Export/src/Processors/EventStoreImportProcessor.php @@ -113,14 +113,14 @@ public function run(): ProcessorResult return ProcessorResult::error(sprintf('Failed to read events. %s is not expected in imported event stream.', $event->type)); } $domainEvent = DecoratedEvent::create($domainEvent, eventId: EventId::fromString($event->identifier), metadata: $event->metadata); - $domainEvents[] = $this->normalizeEvent($domainEvent); + $domainEvents[] = $this->eventNormalizer->normalize($domainEvent); } assert($this->contentStreamId !== null); $contentStreamStreamName = ContentStreamEventStreamName::fromContentStreamId($this->contentStreamId)->getEventStreamName(); $events = Events::with( - $this->normalizeEvent( + $this->eventNormalizer->normalize( new ContentStreamWasCreated( $this->contentStreamId, ) @@ -135,7 +135,7 @@ public function run(): ProcessorResult $workspaceName = WorkspaceName::forLive(); $workspaceStreamName = WorkspaceEventStreamName::fromWorkspaceName($workspaceName)->getEventStreamName(); $events = Events::with( - $this->normalizeEvent( + $this->eventNormalizer->normalize( new RootWorkspaceWasCreated( $workspaceName, WorkspaceTitle::fromString('live workspace'), @@ -158,29 +158,6 @@ public function run(): ProcessorResult return ProcessorResult::success(sprintf('Imported %d event%s into stream "%s"', count($domainEvents), count($domainEvents) === 1 ? '' : 's', $contentStreamStreamName->value)); } - /** - * Copied from {@see EventPersister::normalizeEvent()} - * - * @param EventInterface|DecoratedEvent $event - * @return Event - */ - private function normalizeEvent(EventInterface|DecoratedEvent $event): Event - { - $eventId = $event instanceof DecoratedEvent && $event->eventId !== null ? $event->eventId : EventId::create(); - $eventMetadata = $event instanceof DecoratedEvent ? $event->eventMetadata : null; - $causationId = $event instanceof DecoratedEvent ? $event->causationId : null; - $correlationId = $event instanceof DecoratedEvent ? $event->correlationId : null; - $event = $event instanceof DecoratedEvent ? $event->innerEvent : $event; - return new Event( - $eventId, - $this->eventNormalizer->getEventType($event), - $this->eventNormalizer->getEventData($event), - $eventMetadata, - $causationId, - $correlationId, - ); - } - /** --------------------------- */ /** @@ -194,15 +171,4 @@ private static function extractContentStreamId(array $payload): ContentStreamId } return ContentStreamId::fromString($payload['contentStreamId']); } - - /** - * @phpstan-ignore-next-line currently this private method is unused ... but it does no harm keeping it - */ - private function dispatch(Severity $severity, string $message, mixed ...$args): void - { - $renderedMessage = sprintf($message, ...$args); - foreach ($this->callbacks as $callback) { - $callback($severity, $renderedMessage); - } - } } diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php index a621f5309e2..109da578049 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php @@ -170,11 +170,12 @@ private function resetRuntimeState(): void private function exportEvent(EventInterface $event): void { + $normalizedEvent = $this->eventNormalizer->normalize($event); $exportedEvent = new ExportedEvent( - Uuid::uuid4()->toString(), - $this->eventNormalizer->getEventType($event)->value, - json_decode($this->eventNormalizer->getEventData($event)->value, true), - [] + $normalizedEvent->id->value, + $normalizedEvent->type->value, + json_decode($normalizedEvent->data->value, true), + [], ); assert($this->eventFileResource !== null); fwrite($this->eventFileResource, $exportedEvent->toJson() . chr(10)); From 7096cfe42bf7bcfef39be664fc3ca99c52910539 Mon Sep 17 00:00:00 2001 From: xLaura3m5 Date: Fri, 26 Apr 2024 17:53:29 +0200 Subject: [PATCH 082/214] TASK: add correct autocomplete for new user --- .../Private/Templates/Module/Administration/Users/New.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Neos.Neos/Resources/Private/Templates/Module/Administration/Users/New.html b/Neos.Neos/Resources/Private/Templates/Module/Administration/Users/New.html index b0dfdf9e473..f7572a1977b 100644 --- a/Neos.Neos/Resources/Private/Templates/Module/Administration/Users/New.html +++ b/Neos.Neos/Resources/Private/Templates/Module/Administration/Users/New.html @@ -14,7 +14,7 @@

{neos:backend.translate(id: 'user.new.subtitle', value: 'Create a new user',
- +
@@ -22,14 +22,14 @@

{neos:backend.translate(id: 'user.new.subtitle', value: 'Create a new user',
- +
- +
From 0acaaf1b68e0fad4576bcd25f22e24cbabe36aa3 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Fri, 26 Apr 2024 23:22:56 +0200 Subject: [PATCH 083/214] Introduce and use Nodes::map --- .../Classes/Feature/Common/ConstraintChecks.php | 6 +++--- .../Classes/Feature/NodeMove/NodeMove.php | 6 +++--- .../Classes/Projection/ContentGraph/Nodes.php | 14 +++++++++++--- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index bfcb51b11c2..2ee3b0f4bf2 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -578,7 +578,7 @@ protected function requireNodeAggregateToBeSibling( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findSucceedingSiblingNodes($referenceNodeAggregateId, FindSucceedingSiblingNodesFilter::create()); - if ($succeedingSiblings->getNodeAggregateIds()->contain($siblingNodeAggregateId)) { + if ($succeedingSiblings->mapToNodeAggregateIds()->contain($siblingNodeAggregateId)) { return; } @@ -587,7 +587,7 @@ protected function requireNodeAggregateToBeSibling( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findPrecedingSiblingNodes($referenceNodeAggregateId, FindPrecedingSiblingNodesFilter::create()); - if ($precedingSiblings->getNodeAggregateIds()->contain($siblingNodeAggregateId)) { + if ($precedingSiblings->mapToNodeAggregateIds()->contain($siblingNodeAggregateId)) { return; } @@ -613,7 +613,7 @@ protected function requireNodeAggregateToBeChild( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findChildNodes($parentNodeAggregateId, FindChildNodesFilter::create()); - if ($childNodes->getNodeAggregateIds()->contain($childNodeAggregateId)) { + if ($childNodes->mapToNodeAggregateIds()->contain($childNodeAggregateId)) { return; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index 1f1e3266246..a93f5f0aa8a 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -269,13 +269,13 @@ private function resolveInterdimensionalSiblingsForMove( ? $selectedSubgraph->findSucceedingSiblingNodes( $succeedingSiblingId, FindSucceedingSiblingNodesFilter::create() - )->getNodeAggregateIds() + )->mapToNodeAggregateIds() : null; $alternativePrecedingSiblingIds = $precedingSiblingId ? $selectedSubgraph->findPrecedingSiblingNodes( $precedingSiblingId, FindPrecedingSiblingNodesFilter::create() - )->getNodeAggregateIds() + )->mapToNodeAggregateIds() : null; $interdimensionalSiblings = []; @@ -354,7 +354,7 @@ private function resolveInterdimensionalSiblingsForMove( $variantSucceedingSiblingIds = $variantSubgraph->findSucceedingSiblingNodes( $variantPrecedingSiblingId, FindSucceedingSiblingNodesFilter::create(pagination: Pagination::fromLimitAndOffset(2, 0)) - )->getNodeAggregateIds(); + )->mapToNodeAggregateIds(); $relevantVariantSucceedingSiblingId = null; foreach ($variantSucceedingSiblingIds as $variantSucceedingSiblingId) { if (!$variantSucceedingSiblingId->equals($nodeAggregateId)) { diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php index 43546e4b156..5cc25e8fd00 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php @@ -198,11 +198,19 @@ public function nextAll(Node $referenceNode): self return new self(array_slice($this->nodes, $referenceNodeIndex + 1)); } - public function getNodeAggregateIds(): NodeAggregateIds + /** + * @param \Closure(Node $node): mixed $callback + * @return array + */ + public function map(\Closure $callback): array + { + return array_map($callback, $this->nodes); + } + + public function mapToNodeAggregateIds(): NodeAggregateIds { - return NodeAggregateIds::create(...array_map( + return NodeAggregateIds::create(...$this->map( fn (Node $node): NodeAggregateId => $node->nodeAggregateId, - $this->nodes )); } } From 599092b5b2c04bfa4323332a342647aa7e850ce1 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Sat, 27 Apr 2024 07:18:34 +0000 Subject: [PATCH 084/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/EelHelpersReference.rst | 2 +- .../Documentation/References/FlowQueryOperationReference.rst | 2 +- .../Documentation/References/Signals/ContentRepository.rst | 2 +- Neos.Neos/Documentation/References/Signals/Flow.rst | 2 +- Neos.Neos/Documentation/References/Signals/Media.rst | 2 +- Neos.Neos/Documentation/References/Signals/Neos.rst | 2 +- Neos.Neos/Documentation/References/Validators/Flow.rst | 2 +- Neos.Neos/Documentation/References/Validators/Media.rst | 2 +- Neos.Neos/Documentation/References/Validators/Party.rst | 2 +- .../Documentation/References/ViewHelpers/ContentRepository.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index b8f848f7760..a85ba878dac 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-04-19 +The following reference was automatically generated from code on 2024-04-27 .. _`Neos Command Reference: NEOS.CONTENTREPOSITORY`: diff --git a/Neos.Neos/Documentation/References/EelHelpersReference.rst b/Neos.Neos/Documentation/References/EelHelpersReference.rst index 6c2e1f68828..b17f624d37d 100644 --- a/Neos.Neos/Documentation/References/EelHelpersReference.rst +++ b/Neos.Neos/Documentation/References/EelHelpersReference.rst @@ -3,7 +3,7 @@ Eel Helpers Reference ===================== -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Eel Helpers Reference: Api`: diff --git a/Neos.Neos/Documentation/References/FlowQueryOperationReference.rst b/Neos.Neos/Documentation/References/FlowQueryOperationReference.rst index 68556231ee8..62b66e787ee 100644 --- a/Neos.Neos/Documentation/References/FlowQueryOperationReference.rst +++ b/Neos.Neos/Documentation/References/FlowQueryOperationReference.rst @@ -3,7 +3,7 @@ FlowQuery Operation Reference ============================= -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`FlowQuery Operation Reference: add`: diff --git a/Neos.Neos/Documentation/References/Signals/ContentRepository.rst b/Neos.Neos/Documentation/References/Signals/ContentRepository.rst index 3f59545ec99..686504855ce 100644 --- a/Neos.Neos/Documentation/References/Signals/ContentRepository.rst +++ b/Neos.Neos/Documentation/References/Signals/ContentRepository.rst @@ -3,7 +3,7 @@ Content Repository Signals Reference ==================================== -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Content Repository Signals Reference: Context (``Neos\ContentRepository\Domain\Service\Context``)`: diff --git a/Neos.Neos/Documentation/References/Signals/Flow.rst b/Neos.Neos/Documentation/References/Signals/Flow.rst index b2f7366cb27..233ed55fb2d 100644 --- a/Neos.Neos/Documentation/References/Signals/Flow.rst +++ b/Neos.Neos/Documentation/References/Signals/Flow.rst @@ -3,7 +3,7 @@ Flow Signals Reference ====================== -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Flow Signals Reference: AbstractAdvice (``Neos\Flow\Aop\Advice\AbstractAdvice``)`: diff --git a/Neos.Neos/Documentation/References/Signals/Media.rst b/Neos.Neos/Documentation/References/Signals/Media.rst index c39bf8b3052..571e8194713 100644 --- a/Neos.Neos/Documentation/References/Signals/Media.rst +++ b/Neos.Neos/Documentation/References/Signals/Media.rst @@ -3,7 +3,7 @@ Media Signals Reference ======================= -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Media Signals Reference: AssetCollectionController (``Neos\Media\Browser\Controller\AssetCollectionController``)`: diff --git a/Neos.Neos/Documentation/References/Signals/Neos.rst b/Neos.Neos/Documentation/References/Signals/Neos.rst index ff9496fcd91..16377e2c472 100644 --- a/Neos.Neos/Documentation/References/Signals/Neos.rst +++ b/Neos.Neos/Documentation/References/Signals/Neos.rst @@ -3,7 +3,7 @@ Neos Signals Reference ====================== -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Neos Signals Reference: AbstractCreate (``Neos\Neos\Ui\Domain\Model\Changes\AbstractCreate``)`: diff --git a/Neos.Neos/Documentation/References/Validators/Flow.rst b/Neos.Neos/Documentation/References/Validators/Flow.rst index d6b8a6efac7..eb4e9a47c4a 100644 --- a/Neos.Neos/Documentation/References/Validators/Flow.rst +++ b/Neos.Neos/Documentation/References/Validators/Flow.rst @@ -3,7 +3,7 @@ Flow Validator Reference ======================== -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Flow Validator Reference: AggregateBoundaryValidator`: diff --git a/Neos.Neos/Documentation/References/Validators/Media.rst b/Neos.Neos/Documentation/References/Validators/Media.rst index 6fac21f0bde..e6b879874d6 100644 --- a/Neos.Neos/Documentation/References/Validators/Media.rst +++ b/Neos.Neos/Documentation/References/Validators/Media.rst @@ -3,7 +3,7 @@ Media Validator Reference ========================= -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Media Validator Reference: ImageOrientationValidator`: diff --git a/Neos.Neos/Documentation/References/Validators/Party.rst b/Neos.Neos/Documentation/References/Validators/Party.rst index 5a17caaa024..35039ba2e61 100644 --- a/Neos.Neos/Documentation/References/Validators/Party.rst +++ b/Neos.Neos/Documentation/References/Validators/Party.rst @@ -3,7 +3,7 @@ Party Validator Reference ========================= -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Party Validator Reference: AimAddressValidator`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/ContentRepository.rst b/Neos.Neos/Documentation/References/ViewHelpers/ContentRepository.rst index 184ada559d2..e88beee8763 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/ContentRepository.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/ContentRepository.rst @@ -3,7 +3,7 @@ Content Repository ViewHelper Reference ####################################### -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Content Repository ViewHelper Reference: PaginateViewHelper`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index 4c2a07e8e29..35c85e5d6d0 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index 219e822781b..e732295f38b 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst b/Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst index 9fc00a35312..6e1785ab9ca 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst @@ -3,7 +3,7 @@ Fusion ViewHelper Reference ########################### -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Fusion ViewHelper Reference: fusion:render`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index 92f455de4f2..598bafa2a24 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index e4d659e2b90..66e26ba96b6 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index 104e72bfdaa..216935baa51 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-04-19 +This reference was automatically generated from code on 2024-04-27 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From 7411991a25794a5a220eb3dad6ae9dfe67345ec8 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 27 Apr 2024 12:27:13 +0200 Subject: [PATCH 085/214] 4150 - Enforce uniqueness of name on parent aggregate level --- .../src/Domain/Repository/ContentGraph.php | 13 ++++-- ...NodeAggregateName_ConstraintChecks.feature | 46 ++++++++++++++++--- .../Feature/NodeRenaming/NodeRenaming.php | 17 +++---- 3 files changed, 57 insertions(+), 19 deletions(-) rename Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/{NodeRenaming => 09-NodeRenaming}/01_ChangeNodeAggregateName_ConstraintChecks.feature (60%) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 4781ae1a82e..89115914877 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -201,6 +201,10 @@ public function findNodeAggregateById( } /** + * Parent node aggregates can have a greater dimension space coverage than the given child. + * Thus, it is not enough to just resolve them from the nodes and edges connected to the given child node aggregate. + * Instead, we resolve all parent node aggregate ids instead and fetch the complete aggregates from there. + * * @return iterable */ public function findParentNodeAggregates( @@ -208,12 +212,12 @@ public function findParentNodeAggregates( NodeAggregateId $childNodeAggregateId ): iterable { $queryBuilder = $this->createQueryBuilder() - ->select('pn.*, ph.name, ph.contentstreamid, ph.subtreetags, pdsp.dimensionspacepoint AS covereddimensionspacepoint') + ->distinct() + ->select('pn.nodeaggregateid AS parentNodeAggregateId') ->from($this->tableNamePrefix . '_node', 'pn') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('ch', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') - ->innerJoin('ph', $this->tableNamePrefix . '_dimensionspacepoints', 'pdsp', 'pdsp.hash = ph.dimensionspacepointhash') ->where('cn.nodeaggregateid = :nodeAggregateId') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ch.contentstreamid = :contentStreamId') @@ -222,7 +226,10 @@ public function findParentNodeAggregates( 'contentStreamId' => $contentStreamId->value ]); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); + return NodeAggregates::fromArray(array_filter(array_map( + fn (array $row): ?NodeAggregate => $this->findNodeAggregateById($contentStreamId, NodeAggregateId::fromString($row['parentNodeAggregateId'])), + $this->fetchRows($queryBuilder) + ))); } public function findParentNodeAggregateByChildOriginDimensionSpacePoint( diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature similarity index 60% rename from Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature rename to Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature index 542dbc3fb26..6256baca534 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature @@ -6,7 +6,9 @@ Feature: Change node name These are the base test cases for the NodeAggregateCommandHandler to block invalid commands. Background: - Given using no content dimensions + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | example | general, source, spec, peer | spec->source->general, peer->general | And using the following node types: """yaml 'Neos.ContentRepository.Testing:Content': [] @@ -24,7 +26,7 @@ Feature: Change node name | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | @@ -32,9 +34,9 @@ Feature: Change node name | nodeTypeName | "Neos.ContentRepository:Root" | And the graph projection is fully up to date And the following CreateNodeAggregateWithNode commands are executed: - | nodeAggregateId | nodeName | nodeTypeName | parentNodeAggregateId | initialPropertyValues | tetheredDescendantNodeAggregateIds | - | sir-david-nodenborough | null | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | {} | {"tethered": "nodewyn-tetherton"} | - | nody-mc-nodeface | occupied | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | {} | {} | + | nodeAggregateId | nodeName | nodeTypeName | parentNodeAggregateId | tetheredDescendantNodeAggregateIds | + | sir-david-nodenborough | null | Neos.ContentRepository.Testing:Document | lady-eleonode-rootford | {"tethered": "nodewyn-tetherton"} | + | nody-mc-nodeface | occupied | Neos.ContentRepository.Testing:Document | sir-david-nodenborough | {} | Scenario: Try to rename a node aggregate in a non-existing workspace When the command ChangeNodeAggregateName is executed with payload and exceptions are caught: @@ -44,6 +46,16 @@ Feature: Change node name | newNodeName | "new-name" | Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Scenario: Try to rename a node aggregate in a workspace whose content stream is closed: + When the command CloseContentStream is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + When the command ChangeNodeAggregateName is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | newNodeName | "new-name" | + Then the last command should have thrown an exception of type "ContentStreamIsClosed" + Scenario: Try to rename a non-existing node aggregate When the command ChangeNodeAggregateName is executed with payload and exceptions are caught: | Key | Value | @@ -70,4 +82,26 @@ Feature: Change node name | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | | newNodeName | "tethered" | - Then the last command should have thrown an exception of type "NodeNameIsAlreadyOccupied" + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + + Scenario: Try to rename a node aggregate using a partially occupied name + # Could happen via creation or move with the same effect + Given the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | sourceOrigin | {"example": "source"} | + | targetOrigin | {"example": "peer"} | + And the graph projection is fully up to date + And the command CreateNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | originDimensionSpacePoint | {"example": "peer"} | + | nodeTypeName | "Neos.ContentRepository.Testing:Document" | + | parentNodeAggregateId | "sir-david-nodenborough" | + | nodeName | "esquire" | + And the graph projection is fully up to date + When the command ChangeNodeAggregateName is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | newNodeName | "esquire" | + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php index 40403f523ba..7dbbe43fad4 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php @@ -42,16 +42,13 @@ private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command, $this->requireNodeAggregateToNotBeRoot($nodeAggregate, 'and Root Node Aggregates cannot be renamed'); $this->requireNodeAggregateToBeUntethered($nodeAggregate); foreach ($contentRepository->getContentGraph()->findParentNodeAggregates($contentStreamId, $command->nodeAggregateId) as $parentNodeAggregate) { - foreach ($parentNodeAggregate->occupiedDimensionSpacePoints as $occupiedParentDimensionSpacePoint) { - $this->requireNodeNameToBeUnoccupied( - $contentStreamId, - $command->newNodeName, - $parentNodeAggregate->nodeAggregateId, - $occupiedParentDimensionSpacePoint, - $parentNodeAggregate->coveredDimensionSpacePoints, - $contentRepository - ); - } + $this->requireNodeNameToBeUncovered( + $contentStreamId, + $command->newNodeName, + $parentNodeAggregate->nodeAggregateId, + $parentNodeAggregate->coveredDimensionSpacePoints, + $contentRepository + ); } $events = Events::with( From 5ab793d86958ebf95edd945f265bfffe84ec37e2 Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Sat, 27 Apr 2024 15:14:36 +0200 Subject: [PATCH 086/214] Revert unrelated change to `EventStoreImportProcessor` --- .../src/Processors/EventStoreImportProcessor.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Neos.ContentRepository.Export/src/Processors/EventStoreImportProcessor.php b/Neos.ContentRepository.Export/src/Processors/EventStoreImportProcessor.php index 80b18a9b3dc..df2101f2dc5 100644 --- a/Neos.ContentRepository.Export/src/Processors/EventStoreImportProcessor.php +++ b/Neos.ContentRepository.Export/src/Processors/EventStoreImportProcessor.php @@ -171,4 +171,15 @@ private static function extractContentStreamId(array $payload): ContentStreamId } return ContentStreamId::fromString($payload['contentStreamId']); } + + /** + * @phpstan-ignore-next-line currently this private method is unused ... but it does no harm keeping it + */ + private function dispatch(Severity $severity, string $message, mixed ...$args): void + { + $renderedMessage = sprintf($message, ...$args); + foreach ($this->callbacks as $callback) { + $callback($severity, $renderedMessage); + } + } } From 1909e8cb738ef24b1dfe961e59fa96391d06fb2e Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Sat, 27 Apr 2024 18:04:27 +0200 Subject: [PATCH 087/214] Tweak `Site::getConfiguration()` and add tests --- Neos.Neos/Classes/Domain/Model/Site.php | 13 ++++- .../Tests/Unit/Domain/Model/SiteTest.php | 48 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/Neos.Neos/Classes/Domain/Model/Site.php b/Neos.Neos/Classes/Domain/Model/Site.php index b1d1fac85fd..15c278beaa9 100644 --- a/Neos.Neos/Classes/Domain/Model/Site.php +++ b/Neos.Neos/Classes/Domain/Model/Site.php @@ -380,10 +380,19 @@ public function emitSiteChanged() public function getConfiguration(): SiteConfiguration { - $siteSettingsPath = array_key_exists($this->nodeName, $this->sitesConfiguration) ? $this->nodeName : '*'; + if (array_key_exists($this->nodeName, $this->sitesConfiguration)) { + $siteSettingsPath = $this->nodeName; + } else { + if (!array_key_exists('*', $this->sitesConfiguration)) { + throw new \RuntimeException(sprintf('Missing configuration for "Neos.Neos.sites.%s" or fallback "Neos.Neos.sites.*"', $this->nodeName), 1714230658); + } + $siteSettingsPath = '*'; + } $siteSettings = $this->sitesConfiguration[$siteSettingsPath]; if (isset($siteSettings['preset'])) { - is_string($siteSettings['preset']) || throw new \RuntimeException(sprintf('Invalid "preset" configuration for "Neos.Neos.sites.%s". Expected string, got: %s', $siteSettingsPath, get_debug_type($siteSettings['preset'])), 1699785648); + if (!is_string($siteSettings['preset'])) { + throw new \RuntimeException(sprintf('Invalid "preset" configuration for "Neos.Neos.sites.%s". Expected string, got: %s', $siteSettingsPath, get_debug_type($siteSettings['preset'])), 1699785648); + } if (!isset($this->sitePresetsConfiguration[$siteSettings['preset']]) || !is_array($this->sitePresetsConfiguration[$siteSettings['preset']])) { throw new \RuntimeException(sprintf('Site settings "Neos.Neos.sites.%s" refer to a preset "%s", but no corresponding preset is configured', $siteSettingsPath, $siteSettings['preset']), 1699785736); } diff --git a/Neos.Neos/Tests/Unit/Domain/Model/SiteTest.php b/Neos.Neos/Tests/Unit/Domain/Model/SiteTest.php index b7054ba7a3e..70de94487e1 100644 --- a/Neos.Neos/Tests/Unit/Domain/Model/SiteTest.php +++ b/Neos.Neos/Tests/Unit/Domain/Model/SiteTest.php @@ -12,6 +12,7 @@ */ use Neos\Flow\Tests\UnitTestCase; use Neos\Neos\Domain\Model\Site; +use Neos\Utility\ObjectAccess; /** * Testcase for the "Site" domain model @@ -57,4 +58,51 @@ public function theSiteResourcesPackageKeyCanBeSetAndRetrieved() $site->setSiteResourcesPackageKey('Foo'); self::assertSame('Foo', $site->getSiteResourcesPackageKey()); } + + public static function getConfigurationFailingDataProvider(): iterable + { + yield 'no matching nor default site config' => ['nodeTypeName' => 'siteNodeName', 'sitesConfiguration' => [], 'sitePresetConfiguration' => [], 'expectedExceptionMessage' => 'Missing configuration for "Neos.Neos.sites.siteNodeName" or fallback "Neos.Neos.sites.*"']; + yield 'referring non-string preset' => ['nodeTypeName' => 'siteNodeName', 'sitesConfiguration' => ['siteNodeName' => ['preset' => false]], 'sitePresetConfiguration' => [], 'expectedExceptionMessage' => 'Invalid "preset" configuration for "Neos.Neos.sites.siteNodeName". Expected string, got: bool']; + yield 'referring non-existing preset' => ['nodeTypeName' => 'siteNodeName', 'sitesConfiguration' => ['siteNodeName' => ['preset' => 'nonExistingPreset']], 'sitePresetConfiguration' => [], 'expectedExceptionMessage' => 'Site settings "Neos.Neos.sites.siteNodeName" refer to a preset "nonExistingPreset"']; + yield 'missing content repository identifier' => ['nodeTypeName' => 'siteNodeName', 'sitesConfiguration' => ['siteNodeName' => []], 'sitePresetConfiguration' => [], 'expectedExceptionMessage' => 'There is no content repository identifier configured in Sites configuration in Settings.yaml: Neos.Neos.sites.*.contentRepository']; + yield 'missing content dimension resolver factory' => ['nodeTypeName' => 'siteNodeName', 'sitesConfiguration' => ['siteNodeName' => ['contentRepository' => 'default']], 'sitePresetConfiguration' => [], 'expectedExceptionMessage' => 'No Dimension Resolver Factory configured at Neos.Neos.sites.*.contentDimensions.resolver.factoryClassName']; + } + + /** + * @test + * @dataProvider getConfigurationFailingDataProvider + */ + public function getConfigurationFailingTests(string $nodeTypeName, array $sitesConfiguration, array $sitePresetsConfiguration, string $expectedExceptionMessage): void + { + $site = new Site($nodeTypeName); + ObjectAccess::setProperty($site, 'sitesConfiguration', $sitesConfiguration, true); + ObjectAccess::setProperty($site, 'sitePresetsConfiguration', $sitePresetsConfiguration, true); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + $site->getConfiguration(); + } + + public static function getConfigurationSucceedingDataProvider(): iterable + { + yield 'minimal configuration' => ['nodeTypeName' => 'siteNodeName', 'sitesConfiguration' => ['siteNodeName' => ['contentRepository' => 'default', 'contentDimensions' => ['resolver' => ['factoryClassName' => 'Foo']]]], 'sitePresetConfiguration' => [], 'expectedConfiguration' => ['contentRepositoryId' => 'default', 'contentDimensionResolverFactoryClassName' => 'Foo', 'contentDimensionResolverOptions' => [], 'defaultDimensionSpacePoint' => [], 'uriPathSuffix' => '']]; + yield 'full configuration' => ['nodeTypeName' => 'siteNodeName', 'sitesConfiguration' => ['siteNodeName' => ['contentRepository' => 'custom_repo', 'contentDimensions' => ['resolver' => ['factoryClassName' => 'Bar', 'options' => ['some' => 'options']], 'defaultDimensionSpacePoint' => ['language' => 'de']], 'uriPathSuffix' => 'some-suffix']], 'sitePresetConfiguration' => [], 'expectedConfiguration' => ['contentRepositoryId' => 'custom_repo', 'contentDimensionResolverFactoryClassName' => 'Bar', 'contentDimensionResolverOptions' => ['some' => 'options'], 'defaultDimensionSpacePoint' => ['language' => 'de'], 'uriPathSuffix' => 'some-suffix']]; + yield 'full configuration from fallback' => ['nodeTypeName' => 'siteNodeName', 'sitesConfiguration' => ['*' => ['contentRepository' => 'custom_repo', 'contentDimensions' => ['resolver' => ['factoryClassName' => 'Bar', 'options' => ['some' => 'options']], 'defaultDimensionSpacePoint' => ['language' => 'de']], 'uriPathSuffix' => 'some-suffix']], 'sitePresetConfiguration' => [], 'expectedConfiguration' => ['contentRepositoryId' => 'custom_repo', 'contentDimensionResolverFactoryClassName' => 'Bar', 'contentDimensionResolverOptions' => ['some' => 'options'], 'defaultDimensionSpacePoint' => ['language' => 'de'], 'uriPathSuffix' => 'some-suffix']]; + yield 'full configuration merged with preset' => ['nodeTypeName' => 'siteNodeName', 'sitesConfiguration' => ['siteNodeName' => ['preset' => 'somePreset', 'contentDimensions' => ['defaultDimensionSpacePoint' => ['country' => 'DE']], 'uriPathSuffix' => 'some-overridden-suffix']], 'sitePresetConfiguration' => ['somePreset' => ['contentRepository' => 'custom_repo', 'contentDimensions' => ['resolver' => ['factoryClassName' => 'Bar', 'options' => ['some' => 'options']], 'defaultDimensionSpacePoint' => ['language' => 'de']], 'uriPathSuffix' => 'some-default-suffix']], 'expectedConfiguration' => ['contentRepositoryId' => 'custom_repo', 'contentDimensionResolverFactoryClassName' => 'Bar', 'contentDimensionResolverOptions' => ['some' => 'options'], 'defaultDimensionSpacePoint' => ['language' => 'de', 'country' => 'DE'], 'uriPathSuffix' => 'some-overridden-suffix']]; + yield 'full configuration from fallback merged with preset' => ['nodeTypeName' => 'siteNodeName', 'sitesConfiguration' => ['*' => ['preset' => 'somePreset', 'contentDimensions' => ['defaultDimensionSpacePoint' => ['country' => 'DE']], 'uriPathSuffix' => 'some-overridden-suffix']], 'sitePresetConfiguration' => ['somePreset' => ['contentRepository' => 'custom_repo', 'contentDimensions' => ['resolver' => ['factoryClassName' => 'Bar', 'options' => ['some' => 'options']], 'defaultDimensionSpacePoint' => ['language' => 'de']], 'uriPathSuffix' => 'some-default-suffix']], 'expectedConfiguration' => ['contentRepositoryId' => 'custom_repo', 'contentDimensionResolverFactoryClassName' => 'Bar', 'contentDimensionResolverOptions' => ['some' => 'options'], 'defaultDimensionSpacePoint' => ['language' => 'de', 'country' => 'DE'], 'uriPathSuffix' => 'some-overridden-suffix']]; + } + + /** + * @test + * @dataProvider getConfigurationSucceedingDataProvider + */ + public function getConfigurationSucceedingTests(string $nodeTypeName, array $sitesConfiguration, array $sitePresetsConfiguration, array $expectedConfiguration): void + { + $site = new Site($nodeTypeName); + ObjectAccess::setProperty($site, 'sitesConfiguration', $sitesConfiguration, true); + ObjectAccess::setProperty($site, 'sitePresetsConfiguration', $sitePresetsConfiguration, true); + + $configuration = $site->getConfiguration(); + self::assertSame($expectedConfiguration, json_decode(json_encode($configuration), true)); + } } From ff3b1649426bb9698c2db35d7032c2df520af97c Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 27 Apr 2024 18:11:39 +0200 Subject: [PATCH 088/214] 4150 - refactor ContentGraphs to return single named child node aggregates --- .../src/Domain/Repository/ContentGraph.php | 14 +++++++------ .../Domain/Repository/ContentHypergraph.php | 9 +++------ ...NodeAggregateName_ConstraintChecks.feature | 4 ++-- .../Feature/Common/ConstraintChecks.php | 19 ++++++------------ .../Feature/Common/TetheredNodeInternals.php | 20 +++---------------- .../Classes/Feature/NodeMove/NodeMove.php | 3 --- .../Feature/NodeRenaming/NodeRenaming.php | 1 - .../ContentGraph/ContentGraphInterface.php | 8 +++----- .../Module/Administration/SitesController.php | 8 ++------ .../Domain/Service/SiteServiceInternals.php | 10 ++++------ 10 files changed, 31 insertions(+), 65 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 89115914877..0bbecfb4e7d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -278,18 +278,20 @@ public function findChildNodeAggregates( return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); } - /** - * @return iterable - */ - public function findChildNodeAggregatesByName( + public function findChildNodeAggregateByName( ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId, NodeName $name - ): iterable { + ): ?NodeAggregate { $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $contentStreamId) ->andWhere('ch.name = :relationName') ->setParameter('relationName', $name->value); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); + + return $this->nodeFactory->mapNodeRowsToNodeAggregate( + $this->fetchRows($queryBuilder), + $contentStreamId, + VisibilityConstraints::withoutRestrictions() + ); } /** diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php index 49f9b7af5be..83e2282a116 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php @@ -227,14 +227,11 @@ public function findChildNodeAggregates( ); } - /** - * @return iterable - */ - public function findChildNodeAggregatesByName( + public function findChildNodeAggregateByName( ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId, NodeName $name - ): iterable { + ): ?NodeAggregate { $query = HypergraphChildQuery::create( $contentStreamId, $parentNodeAggregateId, @@ -244,7 +241,7 @@ public function findChildNodeAggregatesByName( $nodeRows = $query->execute($this->getDatabaseConnection())->fetchAllAssociative(); - return $this->nodeFactory->mapNodeRowsToNodeAggregates( + return $this->nodeFactory->mapNodeRowsToNodeAggregate( $nodeRows, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature index 6256baca534..560afeef8ed 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature @@ -77,14 +77,14 @@ Feature: Change node name | newNodeName | "new-name" | Then the last command should have thrown an exception of type "NodeAggregateIsTethered" - Scenario: Try to rename a node aggregate using an already occupied name + Scenario: Try to rename a node aggregate using an already covered name When the command ChangeNodeAggregateName is executed with payload and exceptions are caught: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | | newNodeName | "tethered" | Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" - Scenario: Try to rename a node aggregate using a partially occupied name + Scenario: Try to rename a node aggregate using a partially covered name # Could happen via creation or move with the same effect Given the command CreateNodeVariant is executed with payload: | Key | Value | diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 2ee3b0f4bf2..93b34cdf584 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -662,28 +662,21 @@ protected function requireNodeNameToBeUncovered( ContentStreamId $contentStreamId, ?NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, - DimensionSpacePointSet $dimensionSpacePointsToBeCovered, ContentRepository $contentRepository ): void { if ($nodeName === null) { return; } - $childNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregatesByName( + $childNodeAggregate = $contentRepository->getContentGraph()->findChildNodeAggregateByName( $contentStreamId, $parentNodeAggregateId, $nodeName ); - foreach ($childNodeAggregates as $childNodeAggregate) { - /* @var $childNodeAggregate NodeAggregate */ - $alreadyCoveredDimensionSpacePoints = $childNodeAggregate->coveredDimensionSpacePoints - ->getIntersection($dimensionSpacePointsToBeCovered); - if (!$alreadyCoveredDimensionSpacePoints->isEmpty()) { - throw new NodeNameIsAlreadyCovered( - 'Node name "' . $nodeName->value . '" is already covered in dimension space points ' - . $alreadyCoveredDimensionSpacePoints->toJson() . ' by node aggregate "' - . $childNodeAggregate->nodeAggregateId->value . '".' - ); - } + if ($childNodeAggregate instanceof NodeAggregate) { + throw new NodeNameIsAlreadyCovered( + 'Node name "' . $nodeName->value . '" is already covered by node aggregate "' + . $childNodeAggregate->nodeAggregateId->value . '".' + ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php b/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php index 5d2a01fce2d..e630a562078 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php @@ -63,20 +63,13 @@ protected function createEventsForMissingTetheredNode( NodeType $expectedTetheredNodeType, ContentRepository $contentRepository ): Events { - $childNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregatesByName( + $childNodeAggregate = $contentRepository->getContentGraph()->findChildNodeAggregateByName( $parentNodeAggregate->contentStreamId, $parentNodeAggregate->nodeAggregateId, $tetheredNodeName ); - $tmp = []; - foreach ($childNodeAggregates as $childNodeAggregate) { - $tmp[] = $childNodeAggregate; - } - /** @var array $childNodeAggregates */ - $childNodeAggregates = $tmp; - - if (count($childNodeAggregates) === 0) { + if ($childNodeAggregate === null) { // there is no tethered child node aggregate already; let's create it! $nodeType = $this->nodeTypeManager->requireNodeType($parentNodeAggregate->nodeTypeName); if ($nodeType->isOfType(NodeTypeName::ROOT_NODE_TYPE_NAME)) { @@ -131,9 +124,7 @@ protected function createEventsForMissingTetheredNode( ) ); } - } elseif (count($childNodeAggregates) === 1) { - /** @var NodeAggregate $childNodeAggregate */ - $childNodeAggregate = current($childNodeAggregates); + } else { if (!$childNodeAggregate->classification->isTethered()) { throw new \RuntimeException( 'We found a child node aggregate through the given node path; but it is not tethered.' @@ -155,11 +146,6 @@ protected function createEventsForMissingTetheredNode( $parentNodeAggregate, $contentRepository ); - } else { - throw new \RuntimeException( - 'There is >= 2 ChildNodeAggregates with the same name reachable from the parent' . - '- this is ambiguous and we should analyze how this may happen. That is very likely a bug.' - ); } } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index a93f5f0aa8a..5636f86ca80 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -123,9 +123,6 @@ private function handleMoveNodeAggregate( $contentStreamId, $nodeAggregate->nodeName, $command->newParentNodeAggregateId, - // We need to check all covered DSPs of the parent node aggregate to prevent siblings - // with different node aggregate IDs but the same name - $newParentNodeAggregate->coveredDimensionSpacePoints, $contentRepository ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php index 7dbbe43fad4..86116ddecae 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php @@ -46,7 +46,6 @@ private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command, $contentStreamId, $command->newNodeName, $parentNodeAggregate->nodeAggregateId, - $parentNodeAggregate->coveredDimensionSpacePoints, $contentRepository ); } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 4d8d7e4a4a7..1d9fa4e860b 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -122,17 +122,15 @@ public function findChildNodeAggregates( ): iterable; /** - * A node aggregate may have multiple child node aggregates with the same name - * as long as they do not share dimension space coverage + * A node aggregate can have no or exactly one child node aggregate with a given name as enforced by constraint checks * - * @return iterable * @internal only for consumption inside the Command Handler */ - public function findChildNodeAggregatesByName( + public function findChildNodeAggregateByName( ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId, NodeName $name - ): iterable; + ): ?NodeAggregate; /** * @return iterable diff --git a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php index 24dd502fb69..5671876a527 100755 --- a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php +++ b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php @@ -205,16 +205,12 @@ public function updateSiteAction(Site $site, $newSiteNodeName) } foreach ($contentRepository->getWorkspaceFinder()->findAll() as $workspace) { - // technically, due to the name being the "identifier", there might be more than one :/ - /** @var NodeAggregate[] $siteNodeAggregates */ - /** @var Workspace $workspace */ - $siteNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregatesByName( + $siteNodeAggregate = $contentRepository->getContentGraph()->findChildNodeAggregateByName( $workspace->currentContentStreamId, $sitesNode->nodeAggregateId, $site->getNodeName()->toNodeName() ); - - foreach ($siteNodeAggregates as $siteNodeAggregate) { + if ($siteNodeAggregate instanceof NodeAggregate) { $contentRepository->handle(ChangeNodeAggregateName::create( $workspace->workspaceName, $siteNodeAggregate->nodeAggregateId, diff --git a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php index 7dbe858a5c5..3a9a901f231 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php +++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php @@ -61,14 +61,12 @@ public function removeSiteNode(SiteNodeName $siteNodeName): void $workspace->currentContentStreamId, NodeTypeNameFactory::forSites() ); - $siteNodeAggregates = $contentGraph->findChildNodeAggregatesByName( + $siteNodeAggregate = $contentGraph->findChildNodeAggregateByName( $workspace->currentContentStreamId, $sitesNodeAggregate->nodeAggregateId, $siteNodeName->toNodeName() ); - - foreach ($siteNodeAggregates as $siteNodeAggregate) { - assert($siteNodeAggregate instanceof NodeAggregate); + if ($siteNodeAggregate instanceof NodeAggregate) { $this->contentRepository->handle(RemoveNodeAggregate::create( $workspace->workspaceName, $siteNodeAggregate->nodeAggregateId, @@ -99,12 +97,12 @@ public function createSiteNodeIfNotExists(Site $site, string $nodeTypeName): voi throw SiteNodeTypeIsInvalid::becauseItIsNotOfTypeSite(NodeTypeName::fromString($nodeTypeName)); } - $siteNodeAggregate = $this->contentRepository->getContentGraph()->findChildNodeAggregatesByName( + $siteNodeAggregate = $this->contentRepository->getContentGraph()->findChildNodeAggregateByName( $liveWorkspace->currentContentStreamId, $sitesNodeIdentifier, $site->getNodeName()->toNodeName(), ); - foreach ($siteNodeAggregate as $_) { + if ($siteNodeAggregate instanceof NodeAggregate) { // Site node already exists return; } From 641ab6dd89f728d37b20a29027a9b07b4c548f2c Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 27 Apr 2024 18:45:03 +0200 Subject: [PATCH 089/214] 4150 - Check for node name coverage instead of occupation ...to enforce uniqueness of names on aggregate level --- ...AggregateWithNode_ConstraintChecks.feature | 2 +- ...de_ConstraintChecks_WithDimensions.feature | 33 +++++++++++++++---- .../01-MoveNodes_ConstraintChecks.feature | 32 ++++++++++-------- .../06-AdditionalConstraintChecks.feature | 6 ++-- .../Feature/Common/ConstraintChecks.php | 32 ------------------ .../Feature/NodeCreation/NodeCreation.php | 6 +--- .../NodeDuplicationCommandHandler.php | 8 ++--- .../Exception/NodeNameIsAlreadyOccupied.php | 23 ------------- .../Classes/Command/SiteCommandController.php | 4 +-- .../Module/Administration/SitesController.php | 4 +-- 10 files changed, 56 insertions(+), 94 deletions(-) delete mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Exception/NodeNameIsAlreadyOccupied.php diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature index 7ca3ef355a9..f59456f0558 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature @@ -152,7 +152,7 @@ Feature: Create node aggregate with node | parentNodeAggregateId | "lady-eleonode-rootford" | | nodeName | "document" | - Then the last command should have thrown an exception of type "NodeNameIsAlreadyOccupied" + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" Scenario: Try to create a node aggregate with a property the node type does not declare When the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature index 3fd276365f5..175b6c20db0 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature @@ -9,8 +9,8 @@ Feature: Create node aggregate with node Background: Given using the following content dimensions: - | Identifier | Values | Generalizations | - | language | mul, de, gsw | gsw->de->mul | + | Identifier | Values | Generalizations | + | example | general, source, spec, peer | spec->source->general, peer->general | And using the following node types: """yaml 'Neos.ContentRepository.Testing:Node': @@ -54,16 +54,35 @@ Feature: Create node aggregate with node | nodeAggregateId | "sir-david-nodenborough" | | nodeTypeName | "Neos.ContentRepository.Testing:Node" | | parentNodeAggregateId | "lady-eleonode-rootford" | - | originDimensionSpacePoint | {"language":"gsw"} | + | originDimensionSpacePoint | {"example":"spec"} | And the graph projection is fully up to date And the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | | nodeTypeName | "Neos.ContentRepository.Testing:Node" | | parentNodeAggregateId | "sir-david-nodenborough" | - | originDimensionSpacePoint | {"language":"de"} | + | originDimensionSpacePoint | {"example":"source"} | Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint" + Scenario: Try to create a node aggregate using a name that is already partially covered by one of its siblings + Given the command CreateNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {"example":"peer"} | + | nodeTypeName | "Neos.ContentRepository.Testing:Node" | + | parentNodeAggregateId | "lady-eleonode-rootford" | + | nodeName | "document" | + And the graph projection is fully up to date + When the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | originDimensionSpacePoint | {"example":"source"} | + | nodeTypeName | "Neos.ContentRepository.Testing:Node" | + | parentNodeAggregateId | "lady-eleonode-rootford" | + | nodeName | "document" | + + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + Scenario: Try to create a node aggregate with a root parent and a sibling already claiming the name # root nodes are special in that they have the empty DSP as origin, wich may affect constraint checks When the command CreateNodeAggregateWithNode is executed with payload: @@ -71,7 +90,7 @@ Feature: Create node aggregate with node | nodeAggregateId | "sir-david-nodenborough" | | nodeTypeName | "Neos.ContentRepository.Testing:Node" | | parentNodeAggregateId | "lady-eleonode-rootford" | - | originDimensionSpacePoint | {"language":"de"} | + | originDimensionSpacePoint | {"example":"source"} | | nodeName | "document" | And the graph projection is fully up to date And the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: @@ -79,6 +98,6 @@ Feature: Create node aggregate with node | nodeAggregateId | "nody-mc-nodeface" | | nodeTypeName | "Neos.ContentRepository.Testing:Node" | | parentNodeAggregateId | "lady-eleonode-rootford" | - | originDimensionSpacePoint | {"language":"de"} | + | originDimensionSpacePoint | {"example":"source"} | | nodeName | "document" | - Then the last command should have thrown an exception of type "NodeNameIsAlreadyOccupied" + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature index 47ae7031caf..00f3dbc8266 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature @@ -163,13 +163,19 @@ Feature: Move node to a new parent / within the current parent before a sibling Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePointSet" Scenario: Using the scatter strategy, try to move a node to a parent that already has a child node of the same name - Given the following CreateNodeAggregateWithNode commands are executed: + Given the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-nodeward-nodington-iii" | + | sourceOrigin | {"example": "source"} | + | targetOrigin | {"example": "peer"} | + And the graph projection is fully up to date + And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | - | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | document | + | nody-mc-nodeface | {"example": "peer"} | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | document | When the command MoveNodeAggregate is executed with payload and exceptions are caught: | Key | Value | - | dimensionSpacePoint | {"example": "source"} | + | dimensionSpacePoint | {"example": "peer"} | | nodeAggregateId | "nody-mc-nodeface" | | newParentNodeAggregateId | "lady-eleonode-rootford" | | relationDistributionStrategy | "scatter" | @@ -190,9 +196,9 @@ Feature: Move node to a new parent / within the current parent before a sibling Scenario: Using the gatherAll strategy, try to move a node to a parent that already has a child node of the same name in a generalization Given the following CreateNodeAggregateWithNode commands are executed: - | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | - | rival-destinode | {"example": "general"} | Neos.ContentRepository.Testing:Document | general-nodesworth | target-document | - | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Document | nodimus-prime | target-document | + | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | + | rival-destinode | {"example": "general"} | Neos.ContentRepository.Testing:Document | general-nodesworth | target-document | + | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Document | nodimus-prime | target-document | # Remove the node with the conflicting name in all variants except the generalization And the command RemoveNodeAggregate is executed with payload: | Key | Value | @@ -208,11 +214,11 @@ Feature: Move node to a new parent / within the current parent before a sibling And the graph projection is fully up to date When the command MoveNodeAggregate is executed with payload and exceptions are caught: - | Key | Value | - | dimensionSpacePoint | {"example": "source"} | - | nodeAggregateId | "nody-mc-nodeface" | - | newParentNodeAggregateId | "general-nodesworth" | - | relationDistributionStrategy | "gatherAll" | + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "general-nodesworth" | + | relationDistributionStrategy | "gatherAll" | Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" Scenario: Try to move a node to a parent whose node type does not allow child nodes of the node's type @@ -267,7 +273,7 @@ Feature: Move node to a new parent / within the current parent before a sibling Scenario: Try to move existing node after a node which is not a child of the new parent When the command MoveNodeAggregate is executed with payload and exceptions are caught: | Key | Value | - | dimensionSpacePoint | {"example": "spec"} | + | dimensionSpacePoint | {"example": "spec"} | | nodeAggregateId | "sir-david-nodenborough" | | newParentNodeAggregateId | "anthony-destinode" | | newPrecedingSiblingNodeAggregateId | "sir-nodeward-nodington-iii" | @@ -295,7 +301,7 @@ Feature: Move node to a new parent / within the current parent before a sibling Scenario: Try to move existing node before a node which is not a child of the new parent When the command MoveNodeAggregate is executed with payload and exceptions are caught: | Key | Value | - | dimensionSpacePoint | {"example": "spec"} | + | dimensionSpacePoint | {"example": "spec"} | | nodeAggregateId | "sir-david-nodenborough" | | newParentNodeAggregateId | "anthony-destinode" | | newSucceedingSiblingNodeAggregateId | "sir-nodeward-nodington-iii" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature index a401fe2a00d..a3ecd838bde 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature @@ -60,20 +60,20 @@ Feature: Additional constraint checks after move node capabilities are introduce | nodeTypeName | "Neos.ContentRepository.Testing:Document" | | parentNodeAggregateId | "sir-nodeward-nodington-iii" | | nodeName | "document" | - Then the last command should have thrown an exception of type "NodeNameIsAlreadyOccupied" + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" When the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | | nodeTypeName | "Neos.ContentRepository.Testing:Document" | | parentNodeAggregateId | "lady-abigail-nodenborough" | | nodeName | "document" | - Then the last command should have thrown an exception of type "NodeNameIsAlreadyOccupied" + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" When the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: | Key | Value | | nodeAggregateId | "nody-mc-nodeface" | | nodeTypeName | "Neos.ContentRepository.Testing:Document" | | parentNodeAggregateId | "general-nodesworth" | | nodeName | "document" | - Then the last command should have thrown an exception of type "NodeNameIsAlreadyOccupied" + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 93b34cdf584..db33a2bce60 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -47,7 +47,6 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; use Neos\ContentRepository\Core\SharedModel\Exception\NodeConstraintException; use Neos\ContentRepository\Core\SharedModel\Exception\NodeNameIsAlreadyCovered; -use Neos\ContentRepository\Core\SharedModel\Exception\NodeNameIsAlreadyOccupied; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeIsAbstract; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeIsNotOfTypeRoot; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeIsOfTypeRoot; @@ -624,37 +623,6 @@ protected function requireNodeAggregateToBeChild( ); } - /** - * @throws NodeNameIsAlreadyOccupied - */ - protected function requireNodeNameToBeUnoccupied( - ContentStreamId $contentStreamId, - ?NodeName $nodeName, - NodeAggregateId $parentNodeAggregateId, - OriginDimensionSpacePoint $parentOriginDimensionSpacePoint, - DimensionSpacePointSet $dimensionSpacePoints, - ContentRepository $contentRepository - ): void { - if ($nodeName === null) { - return; - } - $dimensionSpacePointsOccupiedByChildNodeName = $contentRepository->getContentGraph() - ->getDimensionSpacePointsOccupiedByChildNodeName( - $contentStreamId, - $nodeName, - $parentNodeAggregateId, - $parentOriginDimensionSpacePoint, - $dimensionSpacePoints - ); - if (count($dimensionSpacePointsOccupiedByChildNodeName) > 0) { - throw new NodeNameIsAlreadyOccupied( - 'Child node name "' . $nodeName->value . '" is already occupied for parent "' - . $parentNodeAggregateId->value . '" in dimension space points ' - . $dimensionSpacePointsOccupiedByChildNodeName->toJson() - ); - } - } - /** * @throws NodeNameIsAlreadyCovered */ diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index 72ecce321d3..cf0f1a31704 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -171,14 +171,10 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $parentNodeAggregate->coveredDimensionSpacePoints ); if ($command->nodeName) { - $this->requireNodeNameToBeUnoccupied( + $this->requireNodeNameToBeUncovered( $contentStreamId, $command->nodeName, $command->parentNodeAggregateId, - $parentNodeAggregate->classification->isRoot() - ? DimensionSpace\OriginDimensionSpacePoint::createWithoutDimensions() - : $command->originDimensionSpacePoint, - $coveredDimensionSpacePoints, $contentRepository ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php index acac660dfbd..f07da21bf8b 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php @@ -136,16 +136,12 @@ private function handleCopyNodesRecursively( $parentNodeAggregate->coveredDimensionSpacePoints ); - // Constraint: The node name must be free in all these dimension space points + // Constraint: The node name must be free for a new child of the parent node aggregate if ($command->targetNodeName) { - $this->requireNodeNameToBeUnoccupied( + $this->requireNodeNameToBeUncovered( $contentStreamId, $command->targetNodeName, $command->targetParentNodeAggregateId, - $parentNodeAggregate->classification->isRoot() - ? OriginDimensionSpacePoint::createWithoutDimensions() - : $command->targetDimensionSpacePoint, - $coveredDimensionSpacePoints, $contentRepository ); } diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Exception/NodeNameIsAlreadyOccupied.php b/Neos.ContentRepository.Core/Classes/SharedModel/Exception/NodeNameIsAlreadyOccupied.php deleted file mode 100644 index a457d6d1fa9..00000000000 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Exception/NodeNameIsAlreadyOccupied.php +++ /dev/null @@ -1,23 +0,0 @@ -quit(1); - } catch (SiteNodeNameIsAlreadyInUseByAnotherSite | NodeNameIsAlreadyOccupied $exception) { + } catch (SiteNodeNameIsAlreadyInUseByAnotherSite | NodeNameIsAlreadyCovered $exception) { $this->outputLine('A site with siteNodeName "%s" already exists', [$nodeName ?: $name]); $this->quit(1); } diff --git a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php index 5671876a527..8f1dc87c732 100755 --- a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php +++ b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php @@ -18,7 +18,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; -use Neos\ContentRepository\Core\SharedModel\Exception\NodeNameIsAlreadyOccupied; +use Neos\ContentRepository\Core\SharedModel\Exception\NodeNameIsAlreadyCovered; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -412,7 +412,7 @@ public function createSiteNodeAction($packageKey, $siteName, $nodeType) 1412372375 ); $this->redirect('createSiteNode'); - } catch (SiteNodeNameIsAlreadyInUseByAnotherSite | NodeNameIsAlreadyOccupied $exception) { + } catch (SiteNodeNameIsAlreadyInUseByAnotherSite | NodeNameIsAlreadyCovered $exception) { $this->addFlashMessage( $this->getModuleLabel('sites.SiteCreationError.siteWithSiteNodeNameAlreadyExists.body', [$siteName]), $this->getModuleLabel('sites.SiteCreationError.siteWithSiteNodeNameAlreadyExists.title'), From 22b99984771e767eaa48d0b5b9f95c94d3c311d8 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 27 Apr 2024 20:15:03 +0200 Subject: [PATCH 090/214] 4150 - reserve tethered node names ... and prevent manual, erroneous structure adjustments --- ...de_ConstraintChecks_WithDimensions.feature | 41 +++++++++++++++++++ .../01-MoveNodes_ConstraintChecks.feature | 32 +++++++++++++++ ...NodeAggregateName_ConstraintChecks.feature | 18 ++++++++ ...eNodeAggregateType_BasicErrorCases.feature | 4 +- .../Feature/Common/ConstraintChecks.php | 39 ++++++------------ .../Feature/NodeCreation/NodeCreation.php | 4 +- .../NodeDuplicationCommandHandler.php | 1 - .../Classes/Feature/NodeMove/NodeMove.php | 8 +++- .../Feature/NodeRenaming/NodeRenaming.php | 2 + .../Feature/NodeTypeChange/NodeTypeChange.php | 9 ++-- 10 files changed, 120 insertions(+), 38 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature index 175b6c20db0..65f01548c35 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature @@ -101,3 +101,44 @@ Feature: Create node aggregate with node | originDimensionSpacePoint | {"example":"source"} | | nodeName | "document" | Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + + Scenario: Try to create a node aggregate using a name of a not yet existent, tethered child of the parent + Given the command CreateNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "sir-david-nodenborough" | + | nodeTypeName | "Neos.ContentRepository.Testing:Node" | + | parentNodeAggregateId | "lady-eleonode-rootford" | + | originDimensionSpacePoint | {"example":"source"} | + And the graph projection is fully up to date + Given I change the node types in content repository "default" to: + """yaml + 'Neos.ContentRepository.Testing:LeafNode': {} + 'Neos.ContentRepository.Testing:Node': + childNodes: + tethered: + type: 'Neos.ContentRepository.Testing:LeafNode' + properties: + postalAddress: + type: 'Neos\ContentRepository\Core\Tests\Behavior\Fixtures\PostalAddress' + 'Neos.ContentRepository.Testing:NodeWithInvalidPropertyType': + properties: + postalAddress: + type: '\I\Do\Not\Exist' + 'Neos.ContentRepository.Testing:NodeWithInvalidDefaultValue': + properties: + postalAddress: + type: 'Neos\ContentRepository\Core\Tests\Behavior\Fixtures\PostalAddress' + defaultValue: + iDoNotExist: 'whatever' + 'Neos.ContentRepository.Testing:AbstractNode': + abstract: true + """ + # We don't run structure adjustments here on purpose + When the command CreateNodeAggregateWithNode is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | nodeTypeName | "Neos.ContentRepository.Testing:LeafNode" | + | parentNodeAggregateId | "sir-david-nodenborough" | + | originDimensionSpacePoint | {"example":"source"} | + | nodeName | "tethered" | + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature index 00f3dbc8266..b6e8919b959 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature @@ -181,6 +181,38 @@ Feature: Move node to a new parent / within the current parent before a sibling | relationDistributionStrategy | "scatter" | Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + Scenario: Using the scatter (or really any) strategy, try to move a node to a parent that reserves the name for a tethered child + Given I change the node types in content repository "default" to: + """yaml + 'Neos.ContentRepository.Testing:Document': [] + 'Neos.ContentRepository.Testing:Content': + constraints: + nodeTypes: + '*': true + 'Neos.ContentRepository.Testing:Document': false + 'Neos.ContentRepository.Testing:DocumentWithTetheredChildNode': + childNodes: + tethered: + type: 'Neos.ContentRepository.Testing:Content' + constraints: + nodeTypes: + '*': true + 'Neos.ContentRepository.Testing:Content': false + another-tethered: + type: 'Neos.ContentRepository.Testing:Content' + """ + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | + | nody-mc-nodeface | {"example": "source"} | Neos.ContentRepository.Testing:Document | sir-nodeward-nodington-iii | another-tethered | + + When the command MoveNodeAggregate is executed with payload and exceptions are caught: + | Key | Value | + | dimensionSpacePoint | {"example": "source"} | + | nodeAggregateId | "nody-mc-nodeface" | + | newParentNodeAggregateId | "sir-david-nodenborough" | + | relationDistributionStrategy | "scatter" | + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + Scenario: Using the gatherSpecializations strategy, try to move a node to a parent that already has a child node of the same name in a specialization Given the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | originDimensionSpacePoint | nodeTypeName | parentNodeAggregateId | nodeName | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature index 560afeef8ed..bc4fa5f84fc 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature @@ -105,3 +105,21 @@ Feature: Change node name | nodeAggregateId | "nody-mc-nodeface" | | newNodeName | "esquire" | Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" + + Scenario: Try to rename a node aggregate using a name of a not yet existent, tethered child + Given I change the node types in content repository "default" to: + """yaml + 'Neos.ContentRepository.Testing:Content': [] + 'Neos.ContentRepository.Testing:Document': + childNodes: + tethered: + type: 'Neos.ContentRepository.Testing:Content' + another-tethered: + type: 'Neos.ContentRepository.Testing:Content' + """ + # We don't run structure adjustments here on purpose + When the command ChangeNodeAggregateName is executed with payload and exceptions are caught: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | newNodeName | "another-tethered" | + Then the last command should have thrown an exception of type "NodeNameIsAlreadyCovered" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature index 597ab0036b9..9de7da5b20d 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature @@ -135,7 +135,7 @@ Feature: Change node aggregate type - basic error cases | strategy | "happypath" | Then the last command should have thrown an exception of type "NodeConstraintException" - Scenario: Try to change the node type of an auto created child node to anything other than defined: + Scenario: Try to change the node type of an tethered child node: When the command CreateNodeAggregateWithNodeAndSerializedProperties is executed with payload: | Key | Value | | nodeAggregateId | "parent2-na" | @@ -152,4 +152,4 @@ Feature: Change node aggregate type - basic error cases | nodeAggregateId | "nody-mc-nodeface" | | newNodeTypeName | "Neos.ContentRepository.Testing:ParentNodeType" | | strategy | "happypath" | - Then the last command should have thrown an exception of type "NodeConstraintException" + Then the last command should have thrown an exception of type "NodeAggregateIsTethered" diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index db33a2bce60..6b2e92f22a6 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -220,6 +220,17 @@ protected function requireNodeTypeToDeclareReference(NodeTypeName $nodeTypeName, throw ReferenceCannotBeSet::becauseTheNodeTypeDoesNotDeclareIt($referenceName, $nodeTypeName); } + protected function requireNodeTypeNotToDeclareTetheredChildNodeName(NodeTypeName $nodeTypeName, NodeName $nodeName): void + { + $nodeType = $this->requireNodeType($nodeTypeName); + if ($nodeType->hasTetheredNode($nodeName)) { + throw new NodeNameIsAlreadyCovered( + 'Node name "' . $nodeName->value . '" is reserved for a tethered child of parent node aggregate of type "' + . $nodeTypeName->value . '".' + ); + } + } + protected function requireNodeTypeToAllowNodesOfTypeInReference( NodeTypeName $nodeTypeName, ReferenceName $referenceName, @@ -258,16 +269,12 @@ protected function requireNodeTypeToAllowNumberOfReferencesInReference(Serialize /** * NodeType and NodeName must belong together to the same node, which is the to-be-checked one. * - * @param ContentStreamId $contentStreamId - * @param NodeType $nodeType - * @param NodeName|null $nodeName * @param array|NodeAggregateId[] $parentNodeAggregateIds * @throws NodeConstraintException */ protected function requireConstraintsImposedByAncestorsAreMet( ContentStreamId $contentStreamId, NodeType $nodeType, - ?NodeName $nodeName, array $parentNodeAggregateIds, ContentRepository $contentRepository ): void { @@ -280,7 +287,7 @@ protected function requireConstraintsImposedByAncestorsAreMet( if (!$parentAggregate->classification->isTethered()) { try { $parentsNodeType = $this->requireNodeType($parentAggregate->nodeTypeName); - $this->requireNodeTypeConstraintsImposedByParentToBeMet($parentsNodeType, $nodeName, $nodeType); + $this->requireNodeTypeConstraintsImposedByParentToBeMet($parentsNodeType, $nodeType); } catch (NodeTypeNotFound $e) { // skip constraint check; Once the parent is changed to be of an available type, // the constraint checks are executed again. See handleChangeNodeAggregateType @@ -315,7 +322,6 @@ protected function requireConstraintsImposedByAncestorsAreMet( */ protected function requireNodeTypeConstraintsImposedByParentToBeMet( NodeType $parentsNodeType, - ?NodeName $nodeName, NodeType $nodeType ): void { // !!! IF YOU ADJUST THIS METHOD, also adjust the method below. @@ -326,37 +332,16 @@ protected function requireNodeTypeConstraintsImposedByParentToBeMet( 1707561400 ); } - if ( - $nodeName - && $parentsNodeType->hasTetheredNode($nodeName) - && !$this->getNodeTypeManager()->getTypeOfTetheredNode($parentsNodeType, $nodeName)->name->equals($nodeType->name) - ) { - throw new NodeConstraintException( - 'Node type "' . $nodeType->name->value . '" does not match configured "' - . $this->getNodeTypeManager()->getTypeOfTetheredNode($parentsNodeType, $nodeName)->name->value - . '" for auto created child nodes for parent type "' . $parentsNodeType->name->value - . '" with name "' . $nodeName->value . '"', - 1707561404 - ); - } } protected function areNodeTypeConstraintsImposedByParentValid( NodeType $parentsNodeType, - ?NodeName $nodeName, NodeType $nodeType ): bool { // !!! IF YOU ADJUST THIS METHOD, also adjust the method above. if (!$parentsNodeType->allowsChildNodeType($nodeType)) { return false; } - if ( - $nodeName - && $parentsNodeType->hasTetheredNode($nodeName) - && !$this->getNodeTypeManager()->getTypeOfTetheredNode($parentsNodeType, $nodeName)->name->equals($nodeType->name) - ) { - return false; - } return true; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index cf0f1a31704..2dbe6215f97 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -63,6 +63,8 @@ abstract protected function requireNodeTypeToNotBeAbstract(NodeType $nodeType): abstract protected function requireNodeTypeToBeOfTypeRoot(NodeType $nodeType): void; + abstract protected function requireNodeTypeNotToDeclareTetheredChildNodeName(NodeTypeName $nodeTypeName, NodeName $nodeName): void; + abstract protected function getPropertyConverter(): PropertyConverter; abstract protected function getNodeTypeManager(): NodeTypeManager; @@ -138,7 +140,6 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $this->requireConstraintsImposedByAncestorsAreMet( $contentStreamId, $nodeType, - $command->nodeName, [$command->parentNodeAggregateId], $contentRepository ); @@ -177,6 +178,7 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $command->parentNodeAggregateId, $contentRepository ); + $this->requireNodeTypeNotToDeclareTetheredChildNodeName($parentNodeAggregate->nodeTypeName, $command->nodeName); } $descendantNodeAggregateIds = $command->tetheredDescendantNodeAggregateIds->completeForNodeOfType( diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php index f07da21bf8b..687495d116f 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php @@ -97,7 +97,6 @@ private function handleCopyNodesRecursively( $this->requireConstraintsImposedByAncestorsAreMet( $contentStreamId, $nodeType, - $command->targetNodeName, [$command->targetParentNodeAggregateId], $contentRepository ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index 5636f86ca80..e8397501fce 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -28,6 +28,7 @@ use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\RelationDistributionStrategy; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; +use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindPrecedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Pagination\Pagination; @@ -40,6 +41,7 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateIsNoSibling; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** @@ -51,6 +53,8 @@ abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\ abstract protected function areAncestorNodeTypeConstraintChecksEnabled(): bool; + abstract protected function requireNodeTypeNotToDeclareTetheredChildNodeName(NodeTypeName $nodeTypeName, NodeName $nodeName): void; + abstract protected function requireProjectedNodeAggregate( ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId, @@ -108,7 +112,6 @@ private function handleMoveNodeAggregate( $this->requireConstraintsImposedByAncestorsAreMet( $contentStreamId, $this->requireNodeType($nodeAggregate->nodeTypeName), - $nodeAggregate->nodeName, [$command->newParentNodeAggregateId], $contentRepository ); @@ -125,6 +128,9 @@ private function handleMoveNodeAggregate( $command->newParentNodeAggregateId, $contentRepository ); + if ($nodeAggregate->nodeName) { + $this->requireNodeTypeNotToDeclareTetheredChildNodeName($newParentNodeAggregate->nodeTypeName, $nodeAggregate->nodeName); + } $this->requireNodeAggregateToCoverDimensionSpacePoints( $newParentNodeAggregate, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php index 86116ddecae..a989db70d68 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php @@ -22,6 +22,7 @@ use Neos\ContentRepository\Core\Feature\ContentStreamEventStreamName; use Neos\ContentRepository\Core\Feature\NodeRenaming\Command\ChangeNodeAggregateName; use Neos\ContentRepository\Core\Feature\NodeRenaming\Event\NodeAggregateNameWasChanged; +use Neos\ContentRepository\Core\SharedModel\Exception\NodeNameIsAlreadyCovered; /** * @internal implementation detail of Command Handlers @@ -48,6 +49,7 @@ private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command, $parentNodeAggregate->nodeAggregateId, $contentRepository ); + $this->requireNodeTypeNotToDeclareTetheredChildNodeName($parentNodeAggregate->nodeTypeName, $command->newNodeName); } $events = Events::with( diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php index 25e60f27449..f95ed8a3f08 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php @@ -50,6 +50,8 @@ trait NodeTypeChange { abstract protected function getNodeTypeManager(): NodeTypeManager; + abstract protected function requireNodeAggregateToBeUntethered(NodeAggregate $nodeAggregate): void; + abstract protected function requireProjectedNodeAggregate( ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId, @@ -59,20 +61,17 @@ abstract protected function requireProjectedNodeAggregate( abstract protected function requireConstraintsImposedByAncestorsAreMet( ContentStreamId $contentStreamId, NodeType $nodeType, - ?NodeName $nodeName, array $parentNodeAggregateIds, ContentRepository $contentRepository ): void; abstract protected function requireNodeTypeConstraintsImposedByParentToBeMet( NodeType $parentsNodeType, - ?NodeName $nodeName, NodeType $nodeType ): void; abstract protected function areNodeTypeConstraintsImposedByParentValid( NodeType $parentsNodeType, - ?NodeName $nodeName, NodeType $nodeType ): bool; @@ -119,6 +118,7 @@ private function handleChangeNodeAggregateType( $command->nodeAggregateId, $contentRepository ); + $this->requireNodeAggregateToBeUntethered($nodeAggregate); // node type detail checks $this->requireNodeTypeToNotBeOfTypeRoot($newNodeType); @@ -135,7 +135,6 @@ private function handleChangeNodeAggregateType( $this->requireConstraintsImposedByAncestorsAreMet( $contentStreamId, $newNodeType, - $nodeAggregate->nodeName, [$parentNodeAggregate->nodeAggregateId], $contentRepository ); @@ -250,7 +249,6 @@ private function requireConstraintsImposedByHappyPathStrategyAreMet( // so we use $newNodeType (the target node type of $node after the operation) here. $this->requireNodeTypeConstraintsImposedByParentToBeMet( $newNodeType, - $childNodeAggregate->nodeName, $this->requireNodeType($childNodeAggregate->nodeTypeName) ); @@ -301,7 +299,6 @@ private function deleteDisallowedNodesWhenChangingNodeType( !$childNodeAggregate->classification->isTethered() && !$this->areNodeTypeConstraintsImposedByParentValid( $newNodeType, - $childNodeAggregate->nodeName, $this->requireNodeType($childNodeAggregate->nodeTypeName) ) ) { From eff68767dcbaca8801cf4f4e3eb95b56be76f672 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 27 Apr 2024 23:04:53 +0200 Subject: [PATCH 091/214] 4150 - Add test cases for renaming varied and scattered node aggregates --- ...odeAggregateName_ConstraintChecks.feature} | 0 .../02-ChangeNodeAggregateName.feature | 135 ++++++++++++++++++ .../ChangeNodeAggregateName.feature | 84 ----------- 3 files changed, 135 insertions(+), 84 deletions(-) rename Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/{01_ChangeNodeAggregateName_ConstraintChecks.feature => 01-ChangeNodeAggregateName_ConstraintChecks.feature} (100%) create mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/02-ChangeNodeAggregateName.feature delete mode 100644 Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/ChangeNodeAggregateName.feature diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01-ChangeNodeAggregateName_ConstraintChecks.feature similarity index 100% rename from Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature rename to Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/01-ChangeNodeAggregateName_ConstraintChecks.feature diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/02-ChangeNodeAggregateName.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/02-ChangeNodeAggregateName.feature new file mode 100644 index 00000000000..a8507e73c56 --- /dev/null +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/02-ChangeNodeAggregateName.feature @@ -0,0 +1,135 @@ +@contentrepository @adapters=DoctrineDBAL +Feature: Change node aggregate name + + As a user of the CR I want to change the name of a node aggregate + + Background: + Given using the following content dimensions: + | Identifier | Values | Generalizations | + | example | general, source, spec, peer | spec->source->general, peer->general | + And using the following node types: + """yaml + 'Neos.ContentRepository.Testing:Node': {} + 'Neos.ContentRepository.Testing:NodeWithTetheredChildren': + childNodes: + tethered: + type: 'Neos.ContentRepository.Testing:Node' + """ + 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 graph projection is fully up to date + And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | + And the graph projection is fully up to date + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | nodeTypeName | originDimensionSpacePoint | parentNodeAggregateId | nodeName | tetheredDescendantNodeAggregateIds | + | sir-david-nodenborough | Neos.ContentRepository.Testing:Node | {"example":"general"} | lady-eleonode-rootford | parent-document | {} | + | nody-mc-nodeface | Neos.ContentRepository.Testing:NodeWithTetheredChildren | {"example":"source"} | sir-david-nodenborough | document | {"tethered": "nodimus-prime"} | + | nodimus-mediocre | Neos.ContentRepository.Testing:Node | {"example":"source"} | nodimus-prime | grandchild-document | {} | + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | sourceOrigin | {"example":"source"} | + | targetOrigin | {"example":"general"} | + And the graph projection is fully up to date + # leave spec as a virtual variant + And the command CreateNodeVariant is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | sourceOrigin | {"example":"source"} | + | targetOrigin | {"example":"peer"} | + And the graph projection is fully up to date + + Scenario: Rename a child node aggregate with descendants + When the command ChangeNodeAggregateName is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | newNodeName | "renamed-document" | + + Then I expect exactly 11 events to be published on stream with prefix "ContentStream:cs-identifier" + And event at index 10 is of type "NodeAggregateNameWasChanged" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newNodeName | "renamed-document" | + + And I expect the node aggregate "nody-mc-nodeface" to exist + And I expect this node aggregate to be named "renamed-document" + + And I expect the graph projection to consist of exactly 9 nodes + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"general"} + Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to no node + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"source"} + Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"source"} + Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"source"} + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"source"} + Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"source"} + Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"source"} + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"peer"} + Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"peer"} + Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to no node + + Scenario: Rename a scattered node aggregate + Given the command MoveNodeAggregate is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | dimensionSpacePoint | {"example": "peer"} | + | newParentNodeAggregateId | "lady-eleonode-rootford" | + | relationDistributionStrategy | "scatter" | + And the graph projection is fully up to date + + When the command ChangeNodeAggregateName is executed with payload: + | Key | Value | + | nodeAggregateId | "nody-mc-nodeface" | + | newNodeName | "renamed-document" | + + Then I expect exactly 12 events to be published on stream with prefix "ContentStream:cs-identifier" + And event at index 11 is of type "NodeAggregateNameWasChanged" with payload: + | Key | Expected | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nody-mc-nodeface" | + | newNodeName | "renamed-document" | + + And I expect the node aggregate "nody-mc-nodeface" to exist + And I expect this node aggregate to be named "renamed-document" + + And I expect the graph projection to consist of exactly 9 nodes + + When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} + Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"general"} + Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to no node + + When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"source"} + Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"source"} + Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"source"} + + When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"source"} + Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"source"} + Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"source"} + + When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + Then I expect node aggregate identifier "nody-mc-nodeface" and node path "renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"peer"} + Then I expect node aggregate identifier "nodimus-prime" and node path "renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"peer"} + Then I expect node aggregate identifier "nodimus-mediocre" and node path "renamed-document/tethered/grandchild-document" to lead to no node diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/ChangeNodeAggregateName.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/ChangeNodeAggregateName.feature deleted file mode 100644 index e1760070c4e..00000000000 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/ChangeNodeAggregateName.feature +++ /dev/null @@ -1,84 +0,0 @@ -@contentrepository @adapters=DoctrineDBAL -Feature: Change node name - - As a user of the CR I want to change the name of a hierarchical relation between two nodes (e.g. in taxonomies) - - Background: - Given using no content dimensions - And using the following node types: - """yaml - '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" | - | workspaceTitle | "Live" | - | workspaceDescription | "The live workspace" | - | newContentStreamId | "cs-identifier" | - And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} - - And the command CreateRootNodeAggregateWithNode is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-eleonode-rootford" | - | nodeTypeName | "Neos.ContentRepository:Root" | - - Scenario: Change node name of content node - Given the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nody-mc-nodeface" | - | nodeTypeName | "Neos.ContentRepository.Testing:Content" | - | originDimensionSpacePoint | {} | - | coveredDimensionSpacePoints | [{}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "dog" | - | nodeAggregateClassification | "regular" | - - And the graph projection is fully up to date - When the command "ChangeNodeAggregateName" is executed with payload: - | Key | Value | - | workspaceName | "live" | - | nodeAggregateId | "nody-mc-nodeface" | - | newNodeName | "cat" | - - Then I expect exactly 4 events to be published on stream with prefix "ContentStream:cs-identifier" - And event at index 3 is of type "NodeAggregateNameWasChanged" with payload: - | Key | Expected | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nody-mc-nodeface" | - | newNodeName | "cat" | - - Scenario: Change node name actually updates projection - Given the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nody-mc-nodeface" | - | nodeTypeName | "Neos.ContentRepository.Testing:Content" | - | originDimensionSpacePoint | {} | - | coveredDimensionSpacePoints | [{}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "dog" | - | nodeAggregateClassification | "regular" | - And the graph projection is fully up to date - # we read the node initially, to ensure it is filled in the cache (to check whether cache clearing actually works) - When I am in the active content stream of workspace "live" and dimension space point {} - Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} - Then I expect this node to have the following child nodes: - | Name | NodeDiscriminator | - | dog | cs-identifier;nody-mc-nodeface;{} | - - When the command "ChangeNodeAggregateName" is executed with payload: - | Key | Value | - | workspaceName | "live" | - | nodeAggregateId | "nody-mc-nodeface" | - | newNodeName | "cat" | - And the graph projection is fully up to date - - Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} - Then I expect this node to have the following child nodes: - | Name | NodeDiscriminator | - | cat | cs-identifier;nody-mc-nodeface;{} | - From 281efadbbc8b188cb9bb75a3450f21334a7a199e Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 27 Apr 2024 23:10:03 +0200 Subject: [PATCH 092/214] 4150 - Acknowledge that there are already tests for renaming timestamps --- .../Features/NodeTraversal/Timestamps.feature | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature index ed4672f8e85..9af70be68dc 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature @@ -117,7 +117,7 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la When the current date and time is "2023-03-16T13:00:00+01:00" And the command "ChangeNodeAggregateName" is executed with payload: | Key | Value | - | workspaceName | "user-test" | + | workspaceName | "user-test" | | nodeAggregateId | "a" | | newNodeName | "a-renamed" | And the graph projection is fully up to date @@ -135,7 +135,7 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la When the current date and time is "2023-03-16T13:00:00+01:00" And the command SetNodeReferences is executed with payload: | Key | Value | - | workspaceName | "user-test" | + | workspaceName | "user-test" | | sourceOriginDimensionSpacePoint | {"language": "ch"} | | sourceNodeAggregateId | "a" | | referenceName | "ref" | @@ -161,7 +161,7 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la When the current date and time is "2023-03-16T13:00:00+01:00" And the command ChangeNodeAggregateType was published with payload: | Key | Value | - | workspaceName | "user-test" | + | workspaceName | "user-test" | | nodeAggregateId | "a" | | newNodeTypeName | "Neos.ContentRepository.Testing:SpecialPage" | | strategy | "happypath" | @@ -217,7 +217,7 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la When the current date and time is "2023-03-16T13:00:00+01:00" And the command MoveNodeAggregate is executed with payload: | Key | Value | - | workspaceName | "user-test" | + | workspaceName | "user-test" | | dimensionSpacePoint | {"language": "ch"} | | relationDistributionStrategy | "gatherSpecializations" | | nodeAggregateId | "a" | @@ -253,7 +253,7 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la When the current date and time is "2023-03-16T13:00:00+01:00" And the command DisableNodeAggregate is executed with payload: | Key | Value | - | workspaceName | "user-test" | + | workspaceName | "user-test" | | coveredDimensionSpacePoint | {"language": "ch"} | | nodeAggregateId | "a" | | nodeVariantSelectionStrategy | "allSpecializations" | @@ -272,7 +272,7 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la When the current date and time is "2023-03-16T14:00:00+01:00" And the command EnableNodeAggregate is executed with payload: | Key | Value | - | workspaceName | "user-test" | + | workspaceName | "user-test" | | coveredDimensionSpacePoint | {"language": "ch"} | | nodeAggregateId | "a" | | nodeVariantSelectionStrategy | "allSpecializations" | @@ -292,7 +292,7 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la When the current date and time is "2023-03-16T13:00:00+01:00" And the command SetNodeProperties is executed with payload: | Key | Value | - | workspaceName | "user-test" | + | workspaceName | "user-test" | | nodeAggregateId | "a" | | propertyValues | {"text": "Changed"} | And I execute the findNodeById query for node aggregate id "non-existing" I expect no node to be returned From 591486f491db9bca86776872d1e38db84f1bb6ce Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sun, 28 Apr 2024 12:10:44 +0200 Subject: [PATCH 093/214] 4150 - Move node name from hierarchy relation to node --- .../DoctrineDbalContentGraphProjection.php | 9 ----- .../DoctrineDbalContentGraphSchemaBuilder.php | 2 +- .../Projection/Feature/NodeVariation.php | 3 -- .../Domain/Projection/HierarchyRelation.php | 3 -- .../src/Domain/Projection/NodeRecord.php | 3 ++ .../ProjectionIntegrityViolationDetector.php | 4 +-- .../src/Domain/Repository/ContentGraph.php | 14 ++++---- .../src/Domain/Repository/ContentSubgraph.php | 34 +++++++++---------- .../Repository/ProjectionContentGraph.php | 7 ++-- 9 files changed, 33 insertions(+), 46 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index a3a92150aa8..e857b41f9a5 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -446,7 +446,6 @@ private function createNodeWithHierarchy( $node->relationAnchorPoint, new DimensionSpacePointSet([$dimensionSpacePoint]), $succeedingSibling?->relationAnchorPoint, - $nodeName ); } } @@ -457,7 +456,6 @@ private function createNodeWithHierarchy( * @param NodeRelationAnchorPoint $parentNodeAnchorPoint * @param NodeRelationAnchorPoint $childNodeAnchorPoint * @param NodeRelationAnchorPoint|null $succeedingSiblingNodeAnchorPoint - * @param NodeName|null $relationName * @param ContentStreamId $contentStreamId * @param DimensionSpacePointSet $dimensionSpacePointSet * @throws \Doctrine\DBAL\DBALException @@ -468,7 +466,6 @@ private function connectHierarchy( NodeRelationAnchorPoint $childNodeAnchorPoint, DimensionSpacePointSet $dimensionSpacePointSet, ?NodeRelationAnchorPoint $succeedingSiblingNodeAnchorPoint, - NodeName $relationName = null ): void { foreach ($dimensionSpacePointSet as $dimensionSpacePoint) { $position = $this->getRelationPosition( @@ -485,7 +482,6 @@ private function connectHierarchy( $hierarchyRelation = new HierarchyRelation( $parentNodeAnchorPoint, $childNodeAnchorPoint, - $relationName, $contentStreamId, $dimensionSpacePoint, $dimensionSpacePoint->hash, @@ -600,7 +596,6 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void INSERT INTO ' . $this->tableNamePrefix . '_hierarchyrelation ( parentnodeanchor, childnodeanchor, - `name`, position, dimensionspacepointhash, subtreetags, @@ -609,7 +604,6 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void SELECT h.parentnodeanchor, h.childnodeanchor, - h.name, h.position, h.dimensionspacepointhash, h.subtreetags, @@ -782,7 +776,6 @@ protected function copyHierarchyRelationToDimensionSpacePoint( $copy = new HierarchyRelation( $newParent, $newChild, - $sourceHierarchyRelation->name, $contentStreamId, $dimensionSpacePoint, $dimensionSpacePoint->hash, @@ -1002,7 +995,6 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded INSERT INTO ' . $this->tableNamePrefix . '_hierarchyrelation ( parentnodeanchor, childnodeanchor, - `name`, position, subtreetags, dimensionspacepointhash, @@ -1011,7 +1003,6 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded SELECT h.parentnodeanchor, h.childnodeanchor, - h.name, h.position, h.subtreetags, :newDimensionSpacePointHash AS dimensionspacepointhash, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 7722ff41f6f..033fc5fdcb6 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -39,6 +39,7 @@ private function createNodeTable(): Table DbalSchemaFactory::columnForNodeAggregateId('nodeaggregateid')->setNotnull(false), DbalSchemaFactory::columnForDimensionSpacePointHash('origindimensionspacepointhash')->setNotnull(false), DbalSchemaFactory::columnForNodeTypeName('nodetypename'), + (new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('charset', 'ascii')->setCustomSchemaOption('collation', 'ascii_general_ci'), (new Column('properties', Type::getType(Types::TEXT)))->setNotnull(true)->setCustomSchemaOption('collation', self::DEFAULT_TEXT_COLLATION), (new Column('classification', Type::getType(Types::BINARY)))->setLength(20)->setNotnull(true), (new Column('created', Type::getType(Types::DATETIME_IMMUTABLE)))->setDefault('CURRENT_TIMESTAMP')->setNotnull(true), @@ -56,7 +57,6 @@ private function createNodeTable(): Table private function createHierarchyRelationTable(): Table { $table = new Table($this->tableNamePrefix . '_hierarchyrelation', [ - (new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('charset', 'ascii')->setCustomSchemaOption('collation', 'ascii_general_ci'), (new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotnull(true), DbalSchemaFactory::columnForDimensionSpacePointHash('dimensionspacepointhash')->setNotnull(true), diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php index 27b2322512e..c00ccd2d787 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php @@ -107,7 +107,6 @@ private function whenNodeSpecializationVariantWasCreated(NodeSpecializationVaria $hierarchyRelation = new HierarchyRelation( $parentNode->relationAnchorPoint, $specializedNode->relationAnchorPoint, - $sourceNode->nodeName, $event->contentStreamId, $uncoveredDimensionSpacePoint, $uncoveredDimensionSpacePoint->hash, @@ -360,7 +359,6 @@ public function whenNodePeerVariantWasCreated(NodePeerVariantWasCreated $event, $peerNode->relationAnchorPoint, new DimensionSpacePointSet([$coveredDimensionSpacePoint]), $peerSucceedingSiblingNode?->relationAnchorPoint, - $sourceNode->nodeName ); } @@ -393,7 +391,6 @@ abstract protected function connectHierarchy( NodeRelationAnchorPoint $childNodeAnchorPoint, DimensionSpacePointSet $dimensionSpacePointSet, ?NodeRelationAnchorPoint $succeedingSiblingNodeAnchorPoint, - NodeName $relationName = null ): void; abstract protected function copyReferenceRelations( diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php index 8bc02f1e7d3..c0016155b15 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php @@ -18,7 +18,6 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTags; -use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** @@ -31,7 +30,6 @@ public function __construct( public NodeRelationAnchorPoint $parentNodeAnchor, public NodeRelationAnchorPoint $childNodeAnchor, - public ?NodeName $name, public ContentStreamId $contentStreamId, public DimensionSpacePoint $dimensionSpacePoint, public string $dimensionSpacePointHash, @@ -54,7 +52,6 @@ public function addToDatabase(Connection $databaseConnection, string $tableNameP $databaseConnection->insert($tableNamePrefix . '_hierarchyrelation', [ 'parentnodeanchor' => $this->parentNodeAnchor->value, 'childnodeanchor' => $this->childNodeAnchor->value, - 'name' => $this->name?->value, 'contentstreamid' => $this->contentStreamId->value, 'dimensionspacepointhash' => $this->dimensionSpacePointHash, 'position' => $this->position, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php index f9bfb9f6d7d..d18a09f78ef 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php @@ -59,6 +59,7 @@ public function updateToDatabase(Connection $databaseConnection, string $tableNa 'origindimensionspacepointhash' => $this->originDimensionSpacePointHash, 'properties' => json_encode($this->properties), 'nodetypename' => $this->nodeTypeName->value, + 'name' => $this->nodeName?->value, 'classification' => $this->classification->value, 'lastmodified' => $this->timestamps->lastModified, 'originallastmodified' => $this->timestamps->originalLastModified, @@ -145,6 +146,7 @@ public static function createNewInDatabase( $originDimensionSpacePointHash, $properties, $nodeTypeName, + $nodeName, $classification, $timestamps ) { @@ -156,6 +158,7 @@ public static function createNewInDatabase( 'origindimensionspacepointhash' => $originDimensionSpacePointHash, 'properties' => json_encode($properties), 'nodetypename' => $nodeTypeName->value, + 'name' => $nodeName?->value, 'classification' => $classification->value, 'created' => $timestamps->created, 'originalcreated' => $timestamps->originalCreated, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php index adb394fb6cd..2ed14e52b6f 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php @@ -159,7 +159,7 @@ public function tetheredNodesAreNamed(): Result INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint WHERE n.classification = :tethered - AND h.name IS NULL + AND n.name IS NULL GROUP BY n.nodeaggregateid, h.contentstreamid', [ 'tethered' => NodeAggregateClassification::CLASSIFICATION_TETHERED->value @@ -190,7 +190,7 @@ public function subtreeTagsAreInherited(): Result // This could probably be solved with JSON_ARRAY_INTERSECT(JSON_KEYS(ph.subtreetags), JSON_KEYS(h.subtreetags) but unfortunately that's only available with MariaDB 11.2+ according to https://mariadb.com/kb/en/json_array_intersect/ $hierarchyRelationsWithMissingSubtreeTags = $this->client->getConnection()->executeQuery( 'SELECT - ph.name + ph.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ph diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 0bbecfb4e7d..ab7ed9bd58a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -140,7 +140,7 @@ public function findRootNodeAggregates( FindRootNodeAggregatesFilter $filter, ): NodeAggregates { $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->select('n.*, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNamePrefix . '_node', 'n') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') @@ -164,7 +164,7 @@ public function findNodeAggregatesByType( NodeTypeName $nodeTypeName ): iterable { $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->select('n.*, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNamePrefix . '_node', 'n') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') @@ -182,7 +182,7 @@ public function findNodeAggregateById( NodeAggregateId $nodeAggregateId ): ?NodeAggregate { $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->select('n.*, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNamePrefix . '_hierarchyrelation', 'h') ->innerJoin('h', $this->tableNamePrefix . '_node', 'n', 'n.relationanchorpoint = h.childnodeanchor') ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') @@ -248,7 +248,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( ->andWhere('cn.origindimensionspacepointhash = :childOriginDimensionSpacePointHash'); $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->select('n.*, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNamePrefix . '_node', 'n') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') @@ -284,7 +284,7 @@ public function findChildNodeAggregateByName( NodeName $name ): ?NodeAggregate { $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $contentStreamId) - ->andWhere('ch.name = :relationName') + ->andWhere('cn.name = :relationName') ->setParameter('relationName', $name->value); return $this->nodeFactory->mapNodeRowsToNodeAggregate( @@ -325,7 +325,7 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash IN (:dimensionSpacePointHashes)') - ->andWhere('h.name = :nodeName') + ->andWhere('n.name = :nodeName') ->setParameters([ 'parentNodeAggregateId' => $parentNodeAggregateId->value, 'parentNodeOriginDimensionSpacePointHash' => $parentNodeOriginDimensionSpacePoint->hash, @@ -378,7 +378,7 @@ public function getSubgraphs(): array private function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId): QueryBuilder { return $this->createQueryBuilder() - ->select('cn.*, ch.name, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') + ->select('cn.*, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNamePrefix . '_node', 'pn') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index c1a278822d7..a4734791b40 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -169,7 +169,7 @@ public function countBackReferences(NodeAggregateId $nodeAggregateId, CountBackR public function findNodeById(NodeAggregateId $nodeAggregateId): ?Node { $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') + ->select('n.*, h.subtreetags') ->from($this->tableNamePrefix . '_node', 'n') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value) @@ -182,7 +182,7 @@ public function findNodeById(NodeAggregateId $nodeAggregateId): ?Node public function findRootNodeByType(NodeTypeName $nodeTypeName): ?Node { $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') + ->select('n.*, h.subtreetags') ->from($this->tableNamePrefix . '_node', 'n') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') ->where('n.nodetypename = :nodeTypeName')->setParameter('nodeTypeName', $nodeTypeName->value) @@ -197,7 +197,7 @@ public function findRootNodeByType(NodeTypeName $nodeTypeName): ?Node public function findParentNode(NodeAggregateId $childNodeAggregateId): ?Node { $queryBuilder = $this->createQueryBuilder() - ->select('pn.*, ch.name, ch.subtreetags') + ->select('pn.*, ch.subtreetags') ->from($this->tableNamePrefix . '_node', 'pn') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') @@ -239,14 +239,14 @@ public function findNodeByAbsolutePath(AbsoluteNodePath $path): ?Node private function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNodeAggregateId, NodeName $nodeName): ?Node { $queryBuilder = $this->createQueryBuilder() - ->select('cn.*, h.name, h.subtreetags') + ->select('cn.*, h.subtreetags') ->from($this->tableNamePrefix . '_node', 'pn') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = h.childnodeanchor') ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('h.name = :edgeName')->setParameter('edgeName', $nodeName->value); + ->andWhere('cn.name = :nodeName')->setParameter('nodeName', $nodeName->value); $this->addSubtreeTagConstraints($queryBuilder); return $this->fetchNode($queryBuilder); } @@ -290,7 +290,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi { $queryBuilderInitial = $this->createQueryBuilder() // @see https://mariadb.com/kb/en/library/recursive-common-table-expressions-overview/#cast-to-avoid-data-truncation - ->select('n.*, h.name, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') + ->select('n.*, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') ->from($this->tableNamePrefix . '_node', 'n') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') @@ -299,7 +299,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi $this->addSubtreeTagConstraints($queryBuilderInitial); $queryBuilderRecursive = $this->createQueryBuilder() - ->select('c.*, h.name, h.subtreetags, p.nodeaggregateid AS parentNodeAggregateId, p.level + 1 AS level, h.position') + ->select('c.*, h.subtreetags, p.nodeaggregateid AS parentNodeAggregateId, p.level + 1 AS level, h.position') ->from('tree', 'p') ->innerJoin('p', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.parentnodeanchor = p.relationanchorpoint') ->innerJoin('p', $this->tableNamePrefix . '_node', 'c', 'c.relationanchorpoint = h.childnodeanchor') @@ -382,7 +382,7 @@ public function countAncestorNodes(NodeAggregateId $entryNodeAggregateId, CountA public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClosestNodeFilter $filter): ?Node { $queryBuilderInitial = $this->createQueryBuilder() - ->select('n.*, ph.name, ph.subtreetags, ph.parentnodeanchor') + ->select('n.*, ph.subtreetags, ph.parentnodeanchor') ->from($this->tableNamePrefix . '_node', 'n') // we need to join with the hierarchy relation, because we need the node name. ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'n.relationanchorpoint = ph.childnodeanchor') @@ -392,7 +392,7 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose $this->addSubtreeTagConstraints($queryBuilderInitial, 'ph'); $queryBuilderRecursive = $this->createQueryBuilder() - ->select('pn.*, h.name, h.subtreetags, h.parentnodeanchor') + ->select('pn.*, h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') ->innerJoin('cn', $this->tableNamePrefix . '_node', 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = pn.relationanchorpoint') @@ -602,7 +602,7 @@ private function searchPropertyValueStatement(QueryBuilder $queryBuilder, Proper private function buildChildNodesQuery(NodeAggregateId $parentNodeAggregateId, FindChildNodesFilter|CountChildNodesFilter $filter): QueryBuilder { $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') + ->select('n.*, h.subtreetags') ->from($this->tableNamePrefix . '_node', 'pn') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNamePrefix . '_node', 'n', 'h.childnodeanchor = n.relationanchorpoint') @@ -627,7 +627,7 @@ private function buildReferencesQuery(bool $backReferences, NodeAggregateId $nod $sourceTablePrefix = $backReferences ? 'd' : 's'; $destinationTablePrefix = $backReferences ? 's' : 'd'; $queryBuilder = $this->createQueryBuilder() - ->select("{$destinationTablePrefix}n.*, {$destinationTablePrefix}h.name, {$destinationTablePrefix}h.subtreetags, r.name AS referencename, r.properties AS referenceproperties") + ->select("{$destinationTablePrefix}n.*, {$destinationTablePrefix}h.subtreetags, r.name AS referencename, r.properties AS referenceproperties") ->from($this->tableNamePrefix . '_hierarchyrelation', 'sh') ->innerJoin('sh', $this->tableNamePrefix . '_node', 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') ->innerJoin('sh', $this->tableNamePrefix . '_referencerelation', 'r', 'r.nodeanchorpoint = sn.relationanchorpoint') @@ -695,7 +695,7 @@ private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNod ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash'); $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') + ->select('n.*, h.subtreetags') ->from($this->tableNamePrefix . '_node', 'n') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) @@ -727,9 +727,9 @@ private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNod private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId, FindAncestorNodesFilter|CountAncestorNodesFilter|FindClosestNodeFilter $filter): array { $queryBuilderInitial = $this->createQueryBuilder() - ->select('n.*, ph.name, ph.subtreetags, ph.parentnodeanchor') + ->select('n.*, ph.subtreetags, ph.parentnodeanchor') ->from($this->tableNamePrefix . '_node', 'n') - // we need to join with the hierarchy relation, because we need the node name. + // we need to join with the hierarchy relation, because we need the subtree tags. ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') ->innerJoin('ch', $this->tableNamePrefix . '_node', 'c', 'c.relationanchorpoint = ch.childnodeanchor') ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'n.relationanchorpoint = ph.childnodeanchor') @@ -742,7 +742,7 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId $this->addSubtreeTagConstraints($queryBuilderInitial, 'ch'); $queryBuilderRecursive = $this->createQueryBuilder() - ->select('pn.*, h.name, h.subtreetags, h.parentnodeanchor') + ->select('pn.*, h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') ->innerJoin('cn', $this->tableNamePrefix . '_node', 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = pn.relationanchorpoint') @@ -769,7 +769,7 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate { $queryBuilderInitial = $this->createQueryBuilder() // @see https://mariadb.com/kb/en/library/recursive-common-table-expressions-overview/#cast-to-avoid-data-truncation - ->select('n.*, h.name, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') + ->select('n.*, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') ->from($this->tableNamePrefix . '_node', 'n') // we need to join with the hierarchy relation, because we need the node name. ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') @@ -783,7 +783,7 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate $this->addSubtreeTagConstraints($queryBuilderInitial); $queryBuilderRecursive = $this->createQueryBuilder() - ->select('cn.*, h.name, h.subtreetags, pn.nodeaggregateid AS parentNodeAggregateId, pn.level + 1 AS level, h.position') + ->select('cn.*, h.subtreetags, pn.nodeaggregateid AS parentNodeAggregateId, pn.level + 1 AS level, h.position') ->from('tree', 'pn') ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = h.childnodeanchor') diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index 02e6b63e5b9..55b43e2a7cc 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -71,7 +71,7 @@ public function findParentNode( : $originDimensionSpacePoint->hash ]; $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT p.*, ph.contentstreamid, ph.name, ph.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node p + 'SELECT p.*, ph.contentstreamid, ph.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node p INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ph ON ph.childnodeanchor = p.relationanchorpoint INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ch ON ch.parentnodeanchor = p.relationanchorpoint INNER JOIN ' . $this->tableNamePrefix . '_node c ON ch.childnodeanchor = c.relationanchorpoint @@ -102,7 +102,7 @@ public function findNodeInAggregate( DimensionSpacePoint $coveredDimensionSpacePoint ): ?NodeRecord { $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT n.*, h.name, h.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n + 'SELECT n.*, h.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.nodeaggregateid = :nodeAggregateId @@ -131,7 +131,7 @@ public function findNodeByIds( OriginDimensionSpacePoint $originDimensionSpacePoint ): ?NodeRecord { $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT n.*, h.name, h.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n + 'SELECT n.*, h.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.nodeaggregateid = :nodeAggregateId @@ -657,7 +657,6 @@ protected function mapRawDataToHierarchyRelation(array $rawData): HierarchyRelat return new HierarchyRelation( NodeRelationAnchorPoint::fromInteger((int)$rawData['parentnodeanchor']), NodeRelationAnchorPoint::fromInteger((int)$rawData['childnodeanchor']), - $rawData['name'] ? NodeName::fromString($rawData['name']) : null, ContentStreamId::fromString($rawData['contentstreamid']), DimensionSpacePoint::fromJsonString($dimensionspacepointRaw), $rawData['dimensionspacepointhash'], From e2bcf5ca9ad9dc3f93cc7eb2c22d63ec822dc928 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sun, 28 Apr 2024 12:11:21 +0200 Subject: [PATCH 094/214] 4150 - Rename node aggregates with copy on write --- .../DoctrineDbalContentGraphProjection.php | 38 +++++++-------- .../Features/NodeTraversal/Timestamps.feature | 48 +++++++++++++++++++ 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index e857b41f9a5..61c99f595a2 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -359,28 +359,22 @@ private function whenNodeAggregateWithNodeWasCreated(NodeAggregateWithNodeWasCre private function whenNodeAggregateNameWasChanged(NodeAggregateNameWasChanged $event, EventEnvelope $eventEnvelope): void { $this->transactional(function () use ($event, $eventEnvelope) { - $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h - INNER JOIN ' . $this->tableNamePrefix . '_node n on - h.childnodeanchor = n.relationanchorpoint - SET - h.name = :newName, - n.lastmodified = :lastModified, - n.originallastmodified = :originalLastModified - - WHERE - n.nodeaggregateid = :nodeAggregateId - and h.contentstreamid = :contentStreamId - ', [ - 'newName' => $event->newNodeName->value, - 'nodeAggregateId' => $event->nodeAggregateId->value, - 'contentStreamId' => $event->contentStreamId->value, - 'lastModified' => $eventEnvelope->recordedAt, - 'originalLastModified' => self::initiatingDateTime($eventEnvelope), - ], [ - 'lastModified' => Types::DATETIME_IMMUTABLE, - 'originalLastModified' => Types::DATETIME_IMMUTABLE, - ]); + foreach ($this->projectionContentGraph->getAnchorPointsForNodeAggregateInContentStream( + $event->nodeAggregateId, + $event->contentStreamId, + ) as $anchorPoint) { + $this->updateNodeRecordWithCopyOnWrite( + $event->contentStreamId, + $anchorPoint, + function (NodeRecord $node) use ($event, $eventEnvelope) { + $node->nodeName = $event->newNodeName; + $node->timestamps = $node->timestamps->with( + lastModified: $eventEnvelope->recordedAt, + originalLastModified: self::initiatingDateTime($eventEnvelope) + ); + } + ); + } }); } diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature index 9af70be68dc..3a8a6026792 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature @@ -131,6 +131,54 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:30:00 | 2023-03-16 12:30:00 | 2023-03-16 13:00:00 | 2023-03-16 13:00:00 | + Scenario: NodeAggregateNameWasChanged events update last modified timestamps only in the user workspace + When the current date and time is "2023-03-16T13:00:00+01:00" + And the command PublishWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-test" | + And the graph projection is fully up to date + And the command PublishWorkspace is executed with payload: + | Key | Value | + | workspaceName | "review" | + And the graph projection is fully up to date + And the current date and time is "2023-03-16T14:00:00+01:00" + And the command "ChangeNodeAggregateName" is executed with payload: + | Key | Value | + | workspaceName | "user-test" | + | nodeAggregateId | "a" | + | newNodeName | "a-renamed" | + And the graph projection is fully up to date + + And I am in the active content stream of workspace "user-test" and dimension space point {"language":"de"} + Then I expect the node "a" to have the following timestamps: + | created | originalCreated | lastModified | originalLastModified | + | 2023-03-16 13:00:00 | 2023-03-16 12:00:00 | 2023-03-16 14:00:00 | 2023-03-16 14:00:00 | + + And I am in the active content stream of workspace "user-test" and dimension space point {"language":"ch"} + Then I expect the node "a" to have the following timestamps: + | created | originalCreated | lastModified | originalLastModified | + | 2023-03-16 13:00:00 | 2023-03-16 12:30:00 | 2023-03-16 14:00:00 | 2023-03-16 14:00:00 | + + When I am in the active content stream of workspace "review" and dimension space point {"language":"de"} + Then I expect the node "a" to have the following timestamps: + | created | originalCreated | lastModified | originalLastModified | + | 2023-03-16 13:00:00 | 2023-03-16 12:00:00 | | | + + When I am in the active content stream of workspace "review" and dimension space point {"language":"ch"} + Then I expect the node "a" to have the following timestamps: + | created | originalCreated | lastModified | originalLastModified | + | 2023-03-16 13:00:00 | 2023-03-16 12:30:00 | | | + + When I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + Then I expect the node "a" to have the following timestamps: + | created | originalCreated | lastModified | originalLastModified | + | 2023-03-16 13:00:00 | 2023-03-16 12:00:00 | | | + + When I am in the active content stream of workspace "live" and dimension space point {"language":"ch"} + Then I expect the node "a" to have the following timestamps: + | created | originalCreated | lastModified | originalLastModified | + | 2023-03-16 13:00:00 | 2023-03-16 12:30:00 | | | + Scenario: NodeReferencesWereSet events update last modified timestamps When the current date and time is "2023-03-16T13:00:00+01:00" And the command SetNodeReferences is executed with payload: From e0046f8b4e9171dec443ff62396658b7e4ed317b Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sun, 28 Apr 2024 13:02:18 +0200 Subject: [PATCH 095/214] 4150 - Adjust tethered node name integrity checks --- ...ectionIntegrityViolationDetectionTrait.php | 26 +++++-- .../TetheredNodesAreNamed.feature | 67 +++++++++---------- .../DoctrineDbalContentGraphProjection.php | 10 +-- 3 files changed, 59 insertions(+), 44 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php index cb43d749e47..2c0c6bea3e7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php @@ -22,6 +22,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Tests\Behavior\Features\Bootstrap\Helpers\TestingNodeAggregateId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; +use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -123,22 +124,35 @@ public function iChangeTheFollowingHierarchyRelationsDimensionSpacePointHash(Tab } /** - * @When /^I change the following hierarchy relation's name:$/ + * @When /^I change the following node's name:$/ * @param TableNode $payloadTable * @throws DBALException */ - public function iChangeTheFollowingHierarchyRelationsEdgeName(TableNode $payloadTable): void + public function iChangeTheFollowingNodesName(TableNode $payloadTable): void { $dataset = $this->transformPayloadTableToDataset($payloadTable); - $record = $this->transformDatasetToHierarchyRelationRecord($dataset); - unset($record['position']); + + $relationAnchorPoint = $this->dbalClient->getConnection()->executeQuery( + 'SELECT n.relationanchorpoint FROM ' . $this->getTableNamePrefix() . '_node n + JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint + WHERE h.contentstreamid = :contentStreamId + AND n.nodeaggregateId = :nodeAggregateId + AND n.origindimensionspacepointhash = :originDimensionSpacePointHash', + [ + 'contentStreamId' => $dataset['contentStreamId'], + 'nodeAggregateId' => $dataset['nodeAggregateId'], + 'originDimensionSpacePointHash' => OriginDimensionSpacePoint::fromArray($dataset['originDimensionSpacePoint'])->hash, + ] + )->fetchOne(); $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_hierarchyrelation', + $this->getTableNamePrefix() . '_node', [ 'name' => $dataset['newName'] ], - $record + [ + 'relationanchorpoint' => $relationAnchorPoint + ] ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/TetheredNodesAreNamed.feature b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/TetheredNodesAreNamed.feature index 3068aaa7edf..6c697e4970a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/TetheredNodesAreNamed.feature +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/TetheredNodesAreNamed.feature @@ -14,48 +14,47 @@ Feature: Run projection integrity violation detection regarding naming of tether 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" | + | Key | Value | + | workspaceName | "live" | + | workspaceTitle | "Live" | + | workspaceDescription | "The live workspace" | + | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: - | Key | Value | - | nodeAggregateId | "lady-eleonode-rootford" | - | nodeTypeName | "Neos.ContentRepository:Root" | + | Key | Value | + | nodeAggregateId | "lady-eleonode-rootford" | + | nodeTypeName | "Neos.ContentRepository:Root" | And the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "sir-david-nodenborough" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language":"de"} | - | coveredDimensionSpacePoints | [{"language":"de"}] | - | parentNodeAggregateId | "lady-eleonode-rootford" | - | nodeName | "document" | - | nodeAggregateClassification | "regular" | + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "sir-david-nodenborough" | + | nodeTypeName | "Neos.ContentRepository.Testing:Document" | + | originDimensionSpacePoint | {"language":"de"} | + | coveredDimensionSpacePoints | [{"language":"de"}] | + | parentNodeAggregateId | "lady-eleonode-rootford" | + | nodeName | "document" | + | nodeAggregateClassification | "regular" | And the graph projection is fully up to date - Scenario: Create node variants of different type + Scenario: Remove tethered node's name When the event NodeAggregateWithNodeWasCreated was published with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "nodewyn-tetherton" | - | nodeTypeName | "Neos.ContentRepository.Testing:Document" | - | originDimensionSpacePoint | {"language":"de"} | - | coveredDimensionSpacePoints | [{"language":"de"}] | - | parentNodeAggregateId | "sir-david-nodenborough" | - | nodeName | "to-be-hacked-to-null" | - | nodeAggregateClassification | "tethered" | + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "nodewyn-tetherton" | + | nodeTypeName | "Neos.ContentRepository.Testing:Document" | + | originDimensionSpacePoint | {"language":"de"} | + | coveredDimensionSpacePoints | [{"language":"de"}] | + | parentNodeAggregateId | "sir-david-nodenborough" | + | nodeName | "to-be-hacked-to-null" | + | nodeAggregateClassification | "tethered" | And the graph projection is fully up to date - And I change the following hierarchy relation's name: - | Key | Value | - | contentStreamId | "cs-identifier" | - | dimensionSpacePoint | {"language":"de"} | - | parentNodeAggregateId | "sir-david-nodenborough" | - | childNodeAggregateId | "nodewyn-tetherton" | - | newName | null | + And I change the following node's name: + | Key | Value | + | contentStreamId | "cs-identifier" | + | originDimensionSpacePoint | {"language":"de"} | + | nodeAggregateId | "nodewyn-tetherton" | + | newName | null | And I run integrity violation detection Then I expect the integrity violation detection result to contain exactly 1 errors And I expect integrity violation detection result error number 1 to have code 1597923103 diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 61c99f595a2..0dad54c1a01 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -359,10 +359,12 @@ private function whenNodeAggregateWithNodeWasCreated(NodeAggregateWithNodeWasCre private function whenNodeAggregateNameWasChanged(NodeAggregateNameWasChanged $event, EventEnvelope $eventEnvelope): void { $this->transactional(function () use ($event, $eventEnvelope) { - foreach ($this->projectionContentGraph->getAnchorPointsForNodeAggregateInContentStream( - $event->nodeAggregateId, - $event->contentStreamId, - ) as $anchorPoint) { + foreach ( + $this->projectionContentGraph->getAnchorPointsForNodeAggregateInContentStream( + $event->nodeAggregateId, + $event->contentStreamId, + ) as $anchorPoint + ) { $this->updateNodeRecordWithCopyOnWrite( $event->contentStreamId, $anchorPoint, From 8aec86123a371d3a048ea56593c91c011a76a878 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Mon, 29 Apr 2024 08:20:45 +0000 Subject: [PATCH 096/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index 85fa36e4b04..c178a19e3cf 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-04-26 +The following reference was automatically generated from code on 2024-04-29 .. _`Neos Command Reference: NEOS.FLOW`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index d5aa331efdf..8a1af3bca1e 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-04-26 +This reference was automatically generated from code on 2024-04-29 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index cfbd2e01516..232b05da9bf 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-26 +This reference was automatically generated from code on 2024-04-29 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index e549a156d77..a7b0bb6d86f 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-04-26 +This reference was automatically generated from code on 2024-04-29 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index 621f9975b1e..b6dfae2f051 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-26 +This reference was automatically generated from code on 2024-04-29 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index 9dcac440621..e0d864ff97f 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-04-26 +This reference was automatically generated from code on 2024-04-29 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From b0bd1fafa9b428bfbd56e23c20a94002ade1bb21 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Mon, 29 Apr 2024 22:51:13 +0200 Subject: [PATCH 097/214] BUGFIX Flush cache when a linked node has changed --- .../Fusion/Cache/ContentCacheFlusher.php | 6 + .../Features/ContentCache/ConvertUris.feature | 126 ++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index f476deb7166..1d104a0a5ff 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -175,6 +175,12 @@ private function collectTagsForChangeOnNodeIdentifier( $nodeCacheIdentifier->value ); + $dynamicNodeCacheIdentifier = CacheTag::forDynamicNodeAggregate($contentRepositoryId, $contentStreamId, $nodeAggregateId); + $tagsToFlush[$dynamicNodeCacheIdentifier->value] = sprintf( + 'which were tagged with "%s" because that identifier has changed.', + $dynamicNodeCacheIdentifier->value + ); + $descendantOfNodeCacheIdentifier = CacheTag::forDescendantOfNode($contentRepositoryId, $contentStreamId, $nodeAggregateId); $tagsToFlush[$descendantOfNodeCacheIdentifier->value] = sprintf( 'which were tagged with "%s" because node "%s" has changed.', diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature new file mode 100644 index 00000000000..5f089ebfcd0 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature @@ -0,0 +1,126 @@ +@flowEntities +Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag tags + + Background: + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.ContentRepository:Root': {} + 'Neos.Neos:Sites': + superTypes: + 'Neos.ContentRepository:Root': true + 'Neos.Neos:Document': + properties: + title: + type: string + uriPathSegment: + type: string + 'Neos.Neos:Site': + superTypes: + 'Neos.Neos:Document': true + 'Neos.Neos:Test.DocumentType1': + superTypes: + 'Neos.Neos:Document': true + + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And I am user identified by "initiating-user-identifier" + + When the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | newContentStreamId | "cs-identifier" | + And I am in the active content stream of workspace "live" and dimension space point {} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "root" | + | nodeTypeName | "Neos.Neos:Sites" | + And the graph projection is fully up to date + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | + | a | root | Neos.Neos:Site | {} | a | + | a1 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1", "title": "Node a1"} | a1 | + | a2 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a2", "title": "Node a2"} | a2 | + And A site exists for node name "a" and domain "http://localhost" + And the sites configuration is: + """yaml + Neos: + Neos: + sites: + '*': + contentRepository: default + contentDimensions: + resolver: + factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory + """ + And the Fusion context node is "a1" + And the Fusion context request URI is "http://localhost" + And the Fusion renderingMode is "frontend" + And I have the following Fusion setup: + """fusion + include: resource://Neos.Fusion/Private/Fusion/Root.fusion + include: resource://Neos.Neos/Private/Fusion/Root.fusion + + prototype(Neos.Neos:Test.DocumentType1) < prototype(Neos.Fusion:Component) { + + cacheVerifier = ${null} + title = ${q(node).property('title')} + link = Neos.Neos:ConvertUris { + value = ${"Some value with node URI: node://a1."} + } + + renderer = afx` + cacheVerifier={props.cacheVerifier}, + title={props.title}, + link={props.link} + ` + + @cache { + mode = 'cached' + entryIdentifier { + documentNode = ${Neos.Caching.entryIdentifierForNode(node)} + } + entryTags { + 1 = ${Neos.Caching.nodeTag(node)} + } + } + } + + """ + + + Scenario: ContentCache gets flushed when target node changes + #Given I have Fusion content cache enabled + And the Fusion context node is a2 + + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a2, link=Some value with node URI: /a1. + """ + + When the command SetNodeProperties is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "a1" | + | propertyValues | {"uriPathSegment": "a1-new"} | + And the graph projection is fully up to date + And The documenturipath projection is up to date + + And the Fusion context node is a2 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=second execution, title=Node a2, link=Some value with node URI: /a1-new. + """ From 2b54e6735ec4ad200124457a8654cab857546dbf Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Mon, 29 Apr 2024 22:51:30 +0200 Subject: [PATCH 098/214] BUGFIX Flush cache when asset has changed --- .../Features/ContentCache/Nodes.feature | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature new file mode 100644 index 00000000000..839321a2152 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature @@ -0,0 +1,246 @@ +@flowEntities +Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodetype specific tags + + Background: + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.ContentRepository:Root': {} + 'Neos.Neos:Sites': + superTypes: + 'Neos.ContentRepository:Root': true + 'Neos.Neos:Document': + properties: + title: + type: string + uriPathSegment: + type: string + 'Neos.Neos:Site': + superTypes: + 'Neos.Neos:Document': true + 'Neos.Neos:Test.DocumentType1': + superTypes: + 'Neos.Neos:Document': true + 'Neos.Neos:Test.DocumentType2': + superTypes: + 'Neos.Neos:Document': true + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And I am user identified by "initiating-user-identifier" + + When the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | newContentStreamId | "cs-identifier" | + And I am in the active content stream of workspace "live" and dimension space point {} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "root" | + | nodeTypeName | "Neos.Neos:Sites" | + And the graph projection is fully up to date + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | + | a | root | Neos.Neos:Site | {} | site | + | a1 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1", "title": "Node a1"} | a1 | + | a1-1 | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1-1", "title": "Node a1-1"} | a1-1 | + | a2 | a | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a2", "title": "Node a2"} | a2 | + And A site exists for node name "a" and domain "http://localhost" + And the sites configuration is: + """yaml + Neos: + Neos: + sites: + '*': + contentRepository: default + contentDimensions: + resolver: + factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory + """ + And the Fusion context node is "a1" + And the Fusion context request URI is "http://localhost" + And I have the following Fusion setup: + """fusion + include: resource://Neos.Fusion/Private/Fusion/Root.fusion + include: resource://Neos.Neos/Private/Fusion/Root.fusion + + prototype(Neos.Neos:Test.DocumentType1) < prototype(Neos.Fusion:Component) { + + cacheVerifier = ${null} + title = ${q(node).property('title')} + + renderer = afx` + cacheVerifier={props.cacheVerifier}, + title={props.title} + ` + + @cache { + mode = 'cached' + entryIdentifier { + documentNode = ${Neos.Caching.entryIdentifierForNode(node)} + } + entryTags { + 1 = ${Neos.Caching.nodeTag(node)} + 2 = ${Neos.Caching.descendantOfTag(node)} + } + } + } + + prototype(Neos.Neos:Test.DocumentType2) < prototype(Neos.Fusion:Component) { + + cacheVerifier = ${null} + title = ${q(node).property('title')} + + renderer = afx` + cacheVerifier={props.cacheVerifier}, + title={props.title} + ` + + @cache { + mode = 'cached' + entryIdentifier { + documentNode = ${Neos.Caching.entryIdentifierForNode(node)} + } + entryTags { + 1 = ${Neos.Caching.nodeTag(node)} + 2 = ${Neos.Caching.nodeTypeTag('Neos.Neos:Document',node)} + } + } + } + + """ + + + Scenario: ContentCache gets flushed when a property of a node has changed + #Given I have Fusion content cache enabled + And the Fusion context node is a1 + + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a1 + """ + + When the command SetNodeProperties is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "a1" | + | propertyValues | {"title": "Node a1 new"} | + And the graph projection is fully up to date + + And the Fusion context node is a1 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=second execution, title=Node a1 new + """ + + Scenario: ContentCache gets not flushed when a property of another node has changed + #Given I have Fusion content cache enabled + And the Fusion context node is a1 + + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a1 + """ + + When the command SetNodeProperties is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "a2" | + | propertyValues | {"title": "Node a2 new"} | + And the graph projection is fully up to date + + And the Fusion context node is a1 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a1 + """ + + Scenario: ContentCache gets flushed when a property of a node has changed by NodeType name + #Given I have Fusion content cache enabled + And the Fusion context node is a2 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType2 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a2 + """ + + When the command SetNodeProperties is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "a1" | + | propertyValues | {"title": "Node a1 new"} | + And the graph projection is fully up to date + + And the Fusion context node is a2 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType2 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=second execution, title=Node a2 + """ + + Scenario: ContentCache gets flushed when a property of a node has changed of a descendant node + #Given I have Fusion content cache enabled + And the Fusion context node is "a1" + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a1 + """ + + When the command SetNodeProperties is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "a1-1" | + | propertyValues | {"title": "Node a1-1 new"} | + And the graph projection is fully up to date + + And the Fusion context node is "a1" + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=second execution, title=Node a1 + """ \ No newline at end of file From 6318c7fd2fd7ba5d7283b2b9d9b0080c6ace6c6b Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 29 Apr 2024 23:32:00 +0200 Subject: [PATCH 099/214] Update Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php Co-authored-by: Wilhelm Behncke <2522299+grebaldi@users.noreply.github.com> --- Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php b/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php index 250cf0e5ab5..208828be296 100644 --- a/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php +++ b/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php @@ -184,7 +184,7 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void { match ($event::class) { RootWorkspaceWasCreated::class => $this->whenRootWorkspaceWasCreated($event), - NodeAggregateWasMoved::class => $this->whenNodeAggregateWasMoved($event, $eventEnvelope), + NodeAggregateWasMoved::class => $this->whenNodeAggregateWasMoved($event), NodePropertiesWereSet::class => $this->whenNodePropertiesWereSet($event), NodeReferencesWereSet::class => $this->whenNodeReferencesWereSet($event), NodeAggregateWithNodeWasCreated::class => $this->whenNodeAggregateWithNodeWasCreated($event), From 5a7bcfd411eeaabd77b31e1ee99792e9c6ed7c4e Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 29 Apr 2024 23:32:21 +0200 Subject: [PATCH 100/214] Update Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php Co-authored-by: Wilhelm Behncke <2522299+grebaldi@users.noreply.github.com> --- Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php b/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php index 208828be296..a4568f635bf 100644 --- a/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php +++ b/Neos.Neos/Classes/PendingChangesProjection/ChangeProjection.php @@ -231,7 +231,7 @@ private function whenRootWorkspaceWasCreated(RootWorkspaceWasCreated $event): vo } } - private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event, EventEnvelope $eventEnvelope): void + private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event): void { $affectedDimensionSpacePoints = iterator_to_array($event->succeedingSiblingsForCoverage->toDimensionSpacePointSet()); $arbitraryDimensionSpacePoint = reset($affectedDimensionSpacePoints); From 839ea3aa2f804916b96d8e1427bbaba70808bd3d Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 29 Apr 2024 23:33:33 +0200 Subject: [PATCH 101/214] Update Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php Co-authored-by: Wilhelm Behncke <2522299+grebaldi@users.noreply.github.com> --- .../src/Domain/Projection/Feature/NodeMove.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php index 3cf3a3e1387..0c73276f779 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php @@ -96,7 +96,7 @@ private function moveNodeBeforeSucceedingSibling( $newSucceedingSibling = null; if ($succeedingSiblingForCoverage->nodeAggregateId) { - // find the new succeeding sibling NodeRecord; We need this records because we'll use its RelationAnchorPoint later. + // find the new succeeding sibling NodeRecord; We need this record because we'll use its RelationAnchorPoint later. $newSucceedingSibling = $projectionContentGraph->findNodeInAggregate( $contentStreamId, $succeedingSiblingForCoverage->nodeAggregateId, From 56cbfabd019415df8439a82768701f176e17ab9e Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 29 Apr 2024 23:34:06 +0200 Subject: [PATCH 102/214] Update Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php Co-authored-by: Wilhelm Behncke <2522299+grebaldi@users.noreply.github.com> --- .../Classes/Projection/ContentGraph/Nodes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php index 5cc25e8fd00..13fc62b379a 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Nodes.php @@ -207,7 +207,7 @@ public function map(\Closure $callback): array return array_map($callback, $this->nodes); } - public function mapToNodeAggregateIds(): NodeAggregateIds + public function toNodeAggregateIds(): NodeAggregateIds { return NodeAggregateIds::create(...$this->map( fn (Node $node): NodeAggregateId => $node->nodeAggregateId, From 915a5eee952879d7eb2e64b168e981044215bce9 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Tue, 30 Apr 2024 08:33:04 +0200 Subject: [PATCH 103/214] BUGFIX Flush cache when asset has changed --- .../Behavior/Features/ContentCache/ConvertUris.feature | 2 +- .../Tests/Behavior/Features/ContentCache/Nodes.feature | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature index 5f089ebfcd0..a8f09b41c6b 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature @@ -91,7 +91,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag Scenario: ContentCache gets flushed when target node changes - #Given I have Fusion content cache enabled + Given I have Fusion content cache enabled And the Fusion context node is a2 And I execute the following Fusion code: diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature index 839321a2152..f8eacea222b 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature @@ -112,7 +112,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety Scenario: ContentCache gets flushed when a property of a node has changed - #Given I have Fusion content cache enabled + Given I have Fusion content cache enabled And the Fusion context node is a1 And I execute the following Fusion code: @@ -146,7 +146,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety """ Scenario: ContentCache gets not flushed when a property of another node has changed - #Given I have Fusion content cache enabled + Given I have Fusion content cache enabled And the Fusion context node is a1 And I execute the following Fusion code: @@ -180,7 +180,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety """ Scenario: ContentCache gets flushed when a property of a node has changed by NodeType name - #Given I have Fusion content cache enabled + Given I have Fusion content cache enabled And the Fusion context node is a2 And I execute the following Fusion code: """fusion @@ -213,7 +213,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety """ Scenario: ContentCache gets flushed when a property of a node has changed of a descendant node - #Given I have Fusion content cache enabled + Given I have Fusion content cache enabled And the Fusion context node is "a1" And I execute the following Fusion code: """fusion From b3418130d8d29d4c16d49d06e79d3f0fedbf58e6 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Tue, 30 Apr 2024 17:25:40 +0000 Subject: [PATCH 104/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index c178a19e3cf..6bc3a8fb02b 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-04-29 +The following reference was automatically generated from code on 2024-04-30 .. _`Neos Command Reference: NEOS.FLOW`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index 8a1af3bca1e..15ea5624a27 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-04-29 +This reference was automatically generated from code on 2024-04-30 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index 232b05da9bf..4bef0051d26 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-29 +This reference was automatically generated from code on 2024-04-30 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index a7b0bb6d86f..cda6576cce0 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-04-29 +This reference was automatically generated from code on 2024-04-30 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index b6dfae2f051..c08aa275406 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-29 +This reference was automatically generated from code on 2024-04-30 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index e0d864ff97f..114c634cc27 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-04-29 +This reference was automatically generated from code on 2024-04-30 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From 77ffad48500da2a3f57fa97f912ecd32cc05e7bc Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Tue, 30 Apr 2024 22:34:43 +0200 Subject: [PATCH 105/214] Adjust to comments --- .../src/Domain/Projection/Feature/NodeMove.php | 6 +++--- .../Classes/Feature/Common/ConstraintChecks.php | 6 +++--- .../Classes/Feature/NodeMove/NodeMove.php | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php index 0c73276f779..fbd88417383 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php @@ -48,7 +48,7 @@ private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event): void } if ($event->newParentNodeAggregateId) { - $this->moveNodeIntoParent( + $this->moveNodeBeneathParent( $event->contentStreamId, $nodeToBeMoved, $event->newParentNodeAggregateId, @@ -132,10 +132,10 @@ private function moveNodeBeforeSucceedingSibling( * which incoming HierarchyRelation should be moved and where exactly. * * The move target is given as $parentNodeAggregateId and $succeedingSiblingForCoverage. - * We always move to parent after the succeeding sibling if given (or to the end) + * We always move beneath the parent before the succeeding sibling if given (or to the end) * @throws DBALException */ - private function moveNodeIntoParent( + private function moveNodeBeneathParent( ContentStreamId $contentStreamId, NodeRecord $nodeToBeMoved, NodeAggregateId $parentNodeAggregateId, diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 2ee3b0f4bf2..f83f1cf83ae 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -578,7 +578,7 @@ protected function requireNodeAggregateToBeSibling( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findSucceedingSiblingNodes($referenceNodeAggregateId, FindSucceedingSiblingNodesFilter::create()); - if ($succeedingSiblings->mapToNodeAggregateIds()->contain($siblingNodeAggregateId)) { + if ($succeedingSiblings->toNodeAggregateIds()->contain($siblingNodeAggregateId)) { return; } @@ -587,7 +587,7 @@ protected function requireNodeAggregateToBeSibling( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findPrecedingSiblingNodes($referenceNodeAggregateId, FindPrecedingSiblingNodesFilter::create()); - if ($precedingSiblings->mapToNodeAggregateIds()->contain($siblingNodeAggregateId)) { + if ($precedingSiblings->toNodeAggregateIds()->contain($siblingNodeAggregateId)) { return; } @@ -613,7 +613,7 @@ protected function requireNodeAggregateToBeChild( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findChildNodes($parentNodeAggregateId, FindChildNodesFilter::create()); - if ($childNodes->mapToNodeAggregateIds()->contain($childNodeAggregateId)) { + if ($childNodes->toNodeAggregateIds()->contain($childNodeAggregateId)) { return; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index a93f5f0aa8a..b164a54d2a0 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -269,13 +269,13 @@ private function resolveInterdimensionalSiblingsForMove( ? $selectedSubgraph->findSucceedingSiblingNodes( $succeedingSiblingId, FindSucceedingSiblingNodesFilter::create() - )->mapToNodeAggregateIds() + )->toNodeAggregateIds() : null; $alternativePrecedingSiblingIds = $precedingSiblingId ? $selectedSubgraph->findPrecedingSiblingNodes( $precedingSiblingId, FindPrecedingSiblingNodesFilter::create() - )->mapToNodeAggregateIds() + )->toNodeAggregateIds() : null; $interdimensionalSiblings = []; @@ -354,7 +354,7 @@ private function resolveInterdimensionalSiblingsForMove( $variantSucceedingSiblingIds = $variantSubgraph->findSucceedingSiblingNodes( $variantPrecedingSiblingId, FindSucceedingSiblingNodesFilter::create(pagination: Pagination::fromLimitAndOffset(2, 0)) - )->mapToNodeAggregateIds(); + )->toNodeAggregateIds(); $relevantVariantSucceedingSiblingId = null; foreach ($variantSucceedingSiblingIds as $variantSucceedingSiblingId) { if (!$variantSucceedingSiblingId->equals($nodeAggregateId)) { From 54ce32a609c04a6e99d8e21b9a4db4a1e859bca8 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Wed, 1 May 2024 07:58:08 +0000 Subject: [PATCH 106/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index 6bc3a8fb02b..db950b52fb7 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-04-30 +The following reference was automatically generated from code on 2024-05-01 .. _`Neos Command Reference: NEOS.FLOW`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index 15ea5624a27..bfea0f360ff 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-04-30 +This reference was automatically generated from code on 2024-05-01 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index 4bef0051d26..5656681f720 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-30 +This reference was automatically generated from code on 2024-05-01 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index cda6576cce0..4adef5d951f 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-04-30 +This reference was automatically generated from code on 2024-05-01 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index c08aa275406..ea2f404c592 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-30 +This reference was automatically generated from code on 2024-05-01 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index 114c634cc27..8aa200eb4c9 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-04-30 +This reference was automatically generated from code on 2024-05-01 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From 842e00b54848448e1d68a6f0ad1e5baa0ff6629a Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Wed, 1 May 2024 21:38:02 +0200 Subject: [PATCH 107/214] 4993 - Actually execute moveSubtreeTags tests and fix moveSubtreeTags --- .../Projection/Feature/SubtreeTagging.php | 11 ++++-- .../05-MoveNodeAggregate_SubtreeTags.feature | 34 +++++++++---------- .../Projection/ContentGraph/NodeTags.php | 2 +- .../Features/Bootstrap/ProjectedNodeTrait.php | 22 +++++++----- 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 976526a10de..281e070e817 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -139,11 +139,14 @@ private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregate $nodeTags = $this->nodeTagsForNode($nodeAggregateId, $contentStreamId, $coveredDimensionSpacePoint); $newParentSubtreeTags = $this->nodeTagsForNode($newParentNodeAggregateId, $contentStreamId, $coveredDimensionSpacePoint); $newSubtreeTags = []; + $newDescendantSubtreeTags = []; foreach ($nodeTags->withoutInherited() as $tag) { $newSubtreeTags[$tag->value] = true; + $newDescendantSubtreeTags[$tag->value] = null; } foreach ($newParentSubtreeTags as $tag) { - $newSubtreeTags[$tag->value] = null; + $newSubtreeTags[$tag->value] ??= null; + $newDescendantSubtreeTags[$tag->value] = null; } if ($newSubtreeTags === [] && $nodeTags->isEmpty()) { return; @@ -151,7 +154,9 @@ private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregate $this->getDatabaseConnection()->executeStatement(' UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h SET h.subtreetags = JSON_MERGE_PATCH(:newParentTags, JSON_MERGE_PATCH(\'{}\', h.subtreetags)) - WHERE h.childnodeanchor IN ( + WHERE h.contentstreamid = :contentStreamId + AND h.dimensionspacepointhash = :dimensionSpacePointHash + AND h.childnodeanchor IN ( WITH RECURSIVE cte (id) AS ( SELECT ch.childnodeanchor FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation ch @@ -173,7 +178,7 @@ private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregate 'contentStreamId' => $contentStreamId->value, 'nodeAggregateId' => $nodeAggregateId->value, 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, - 'newParentTags' => json_encode($newSubtreeTags, JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), + 'newParentTags' => json_encode($newDescendantSubtreeTags, JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), ]); $this->getDatabaseConnection()->executeStatement(' UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature index eb7d9ed22e4..ea431988e7d 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature @@ -458,7 +458,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -469,7 +469,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -539,7 +539,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -609,7 +609,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -679,7 +679,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1020,7 +1020,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1031,7 +1031,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1101,7 +1101,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1171,7 +1171,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1241,7 +1241,7 @@ Feature: Move a node aggregate into and out of a tagged parent Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" - And I expect this node to exactly inherit the tags "tag1" + And I expect this node to exactly inherit the tags "" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} And I expect this node to be a child of node cs-identifier;nody-mc-nodeface;{"example":"general"} @@ -1788,7 +1788,7 @@ Feature: Move a node aggregate into and out of a tagged parent When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} - And I expect this node to be exactly explicitly tagged "tag1" + And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" And I expect node aggregate identifier "nodimus-mediocre" and node path "esquire/esquire-child/document/child-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"general"} @@ -1811,7 +1811,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -1881,7 +1881,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -1951,7 +1951,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "source"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -2021,7 +2021,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "source"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -2091,7 +2091,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | @@ -2161,7 +2161,7 @@ Feature: Move a node aggregate into and out of a tagged parent Given the command TagSubtree is executed with payload: | Key | Value | - | nodeAggregateId | "nody-mc-nodeface" | + | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"example": "spec"} | | nodeVariantSelectionStrategy | "allSpecializations" | | tag | "tag1" | diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php index 8317643ab22..06794251a50 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeTags.php @@ -23,7 +23,7 @@ * * Internally, this consists of two collection: * - One for tags that are _explicitly_ set on the respective node. - * - And one that contains tags that are _inherited_ by one of the ancestor nodes + * - And one that contains tags that are only _inherited_ by one of the ancestor nodes and _not_ explicitly set * * In most cases, it shouldn't matter whether a tag is inherited or set explicitly. But sometimes the behavior is slightly different (e.g. the "disabled" checkbox in the Neos UI inspector is only checked if the node has the `disabled` tag set explicitly) * diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index bee6d1ffec9..05d64f2338e 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -243,23 +243,29 @@ public function iExpectTheNodeWithAggregateIdentifierToNotContainTheTag(string $ /** * @Then /^I expect this node to be exactly explicitly tagged "(.*)"$/ - * @param string $tagList the comma-separated list of tag names + * @param string $expectedTagList the comma-separated list of tag names */ - public function iExpectThisNodeToBeExactlyExplicitlyTagged(string $tagList): void + public function iExpectThisNodeToBeExactlyExplicitlyTagged(string $expectedTagList): void { - $this->assertOnCurrentNode(function (Node $currentNode) use ($tagList) { - $currentNode->tags->withoutInherited()->toStringArray() === explode(',', $tagList); + $this->assertOnCurrentNode(function (Node $currentNode) use ($expectedTagList) { + Assert::assertSame( + ($expectedTagList === '') ? [] : explode(',', $expectedTagList), + $currentNode->tags->withoutInherited()->toStringArray() + ); }); } /** * @Then /^I expect this node to exactly inherit the tags "(.*)"$/ - * @param string $tagList the comma-separated list of tag names + * @param string $expectedTagList the comma-separated list of tag names */ - public function iExpectThisNodeToExactlyInheritTheTags(string $tagList): void + public function iExpectThisNodeToExactlyInheritTheTags(string $expectedTagList): void { - $this->assertOnCurrentNode(function (Node $currentNode) use ($tagList) { - $currentNode->tags->onlyInherited()->toStringArray() === explode(',', $tagList); + $this->assertOnCurrentNode(function (Node $currentNode) use ($expectedTagList) { + Assert::assertSame( + ($expectedTagList === '') ? [] : explode(',', $expectedTagList), + $currentNode->tags->onlyInherited()->toStringArray(), + ); }); } From 9c54dc2968373c47d43f8beb3a20ff3c8fb521bb Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Thu, 2 May 2024 16:36:43 +0200 Subject: [PATCH 108/214] BUGFIX: Fix Subtree tagging CTE implementation and make tests more reliable by sorting tags --- .../Projection/Feature/SubtreeTagging.php | 90 +++++-------------- .../TagSubtree_WithoutDimensions.feature | 34 +++---- .../Features/Bootstrap/NodeTraversalTrait.php | 6 +- 3 files changed, 46 insertions(+), 84 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 281e070e817..56fd8440ffb 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -136,84 +136,42 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId, NodeAggregateId $newParentNodeAggregateId, DimensionSpacePoint $coveredDimensionSpacePoint): void { - $nodeTags = $this->nodeTagsForNode($nodeAggregateId, $contentStreamId, $coveredDimensionSpacePoint); - $newParentSubtreeTags = $this->nodeTagsForNode($newParentNodeAggregateId, $contentStreamId, $coveredDimensionSpacePoint); - $newSubtreeTags = []; - $newDescendantSubtreeTags = []; - foreach ($nodeTags->withoutInherited() as $tag) { - $newSubtreeTags[$tag->value] = true; - $newDescendantSubtreeTags[$tag->value] = null; - } - foreach ($newParentSubtreeTags as $tag) { - $newSubtreeTags[$tag->value] ??= null; - $newDescendantSubtreeTags[$tag->value] = null; - } - if ($newSubtreeTags === [] && $nodeTags->isEmpty()) { - return; - } $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h - SET h.subtreetags = JSON_MERGE_PATCH(:newParentTags, JSON_MERGE_PATCH(\'{}\', h.subtreetags)) - WHERE h.contentstreamid = :contentStreamId - AND h.dimensionspacepointhash = :dimensionSpacePointHash - AND h.childnodeanchor IN ( - WITH RECURSIVE cte (id) AS ( - SELECT ch.childnodeanchor - FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation ch - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = ch.parentnodeanchor - WHERE - n.nodeaggregateid = :nodeAggregateId - AND ch.contentstreamid = :contentStreamId - AND ch.dimensionspacepointhash = :dimensionSpacePointHash - UNION ALL + UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h, + ( + WITH RECURSIVE cte AS ( SELECT - dh.childnodeanchor + JSON_KEYS(th.subtreetags) subtreeTagsToInherit, th.childnodeanchor + FROM + ' . $this->getTableNamePrefix() . '_hierarchyrelation th + INNER JOIN ' . $this->getTableNamePrefix() . '_node tn ON tn.relationanchorpoint = th.childnodeanchor + WHERE + tn.nodeaggregateid = :nodeAggregateId + AND th.contentstreamid = :contentStreamId + AND th.dimensionspacepointhash = :dimensionSpacePointHash + UNION + SELECT JSON_MERGE(cte.subtreetagsToInherit, JSON_KEYS(JSON_MERGE_PATCH(\'{}\', dh.subtreetags))) subtreeTagsToInherit, dh.childnodeanchor FROM cte - JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.id + JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.childnodeanchor ) - SELECT id FROM cte + SELECT * FROM cte + ) AS r + SET h.subtreetags = ( + SELECT + JSON_MERGE_PATCH(JSON_OBJECTAGG(htk.k, null), JSON_MERGE_PATCH(\'{}\', h.subtreetags)) + FROM + JSON_TABLE(r.subtreeTagsToInherit, \'$[*]\' COLUMNS (k VARCHAR(36) PATH \'$\')) htk ) - ', [ - 'contentStreamId' => $contentStreamId->value, - 'nodeAggregateId' => $nodeAggregateId->value, - 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, - 'newParentTags' => json_encode($newDescendantSubtreeTags, JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), - ]); - $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = h.childnodeanchor - SET h.subtreetags = :newParentTags WHERE - n.nodeaggregateid = :nodeAggregateId + h.childnodeanchor = r.childnodeanchor AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash - ', [ - 'contentStreamId' => $contentStreamId->value, - 'nodeAggregateId' => $nodeAggregateId->value, - 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, - 'newParentTags' => json_encode($newSubtreeTags, JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), - ]); - } - - private function nodeTagsForNode(NodeAggregateId $nodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): NodeTags - { - $subtreeTagsJson = $this->getDatabaseConnection()->fetchOne(' - SELECT h.subtreetags FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation h - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = h.childnodeanchor - WHERE - n.nodeaggregateid = :nodeAggregateId - AND h.contentstreamid = :contentStreamId - AND h.dimensionspacepointhash = :dimensionSpacePointHash ', [ - 'nodeAggregateId' => $nodeAggregateId->value, 'contentStreamId' => $contentStreamId->value, - 'dimensionSpacePointHash' => $dimensionSpacePoint->hash, + 'nodeAggregateId' => $newParentNodeAggregateId->value, + 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, ]); - if (!is_string($subtreeTagsJson)) { - throw new \RuntimeException(sprintf('Failed to fetch SubtreeTags for node "%s" in content subgraph "%s@%s"', $nodeAggregateId->value, $dimensionSpacePoint->toJson(), $contentStreamId->value), 1698838865); - } - return NodeFactory::extractNodeTagsFromJson($subtreeTagsJson); } private function subtreeTagsForHierarchyRelation(ContentStreamId $contentStreamId, NodeRelationAnchorPoint $parentNodeAnchorPoint, DimensionSpacePoint $dimensionSpacePoint): NodeTags diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature index c88e62f53b3..6dc797cfa53 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature @@ -173,11 +173,11 @@ Feature: Tag subtree without dimensions """ b (tag2*) b1 (tag3*,tag2) - a1a (tag4*,tag3,tag2) - a1a1 (tag4*,tag1*,tag3,tag2) - a1a1a (tag4*,tag3,tag2) - a1a1b (tag4*,tag3,tag2) - a1a2 (tag4*,tag3,tag2) + a1a (tag4*,tag2,tag3) + a1a1 (tag1*,tag2,tag3,tag4) + a1a1a (tag1,tag2,tag3,tag4) + a1a1b (tag1,tag2,tag3,tag4) + a1a2 (tag2,tag3,tag4) """ When the command CreateNodeAggregateWithNode is executed with payload: @@ -189,12 +189,12 @@ Feature: Tag subtree without dimensions """ b (tag2*) b1 (tag3*,tag2) - a1a (tag4*,tag3,tag2) - a1a1 (tag4*,tag1*,tag3,tag2) - a1a1a (tag4*,tag3,tag2) - a1a1b (tag4*,tag3,tag2) - a1a2 (tag4*,tag3,tag2) - a1a3 (tag4,tag3,tag2) + a1a (tag4*,tag2,tag3) + a1a1 (tag1*,tag2,tag3,tag4) + a1a1a (tag1,tag2,tag3,tag4) + a1a1b (tag1,tag2,tag3,tag4) + a1a2 (tag2,tag3,tag4) + a1a3 (tag2,tag3,tag4) """ When the command UntagSubtree is executed with payload: @@ -206,10 +206,10 @@ Feature: Tag subtree without dimensions """ b (tag2*) b1 (tag3*,tag2) - a1a (tag3,tag2) - a1a1 (tag4*,tag1*,tag3,tag2) - a1a1a (tag4*,tag3,tag2) - a1a1b (tag4*,tag3,tag2) - a1a2 (tag4*,tag3,tag2) - a1a3 (tag3,tag2) + a1a (tag2,tag3) + a1a1 (tag1*,tag2,tag3) + a1a1a (tag1,tag2,tag3) + a1a1b (tag1,tag2,tag3) + a1a2 (tag2,tag3) + a1a3 (tag2,tag3) """ diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php index 119ed394de0..098598b9c2a 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/NodeTraversalTrait.php @@ -248,7 +248,11 @@ public function iExecuteTheFindSubtreeQueryIExpectTheFollowingTrees(string $entr $subtree = array_shift($subtreeStack); $tags = []; if ($withTags !== null) { - $tags = [...array_map(static fn(string $tag) => $tag . '*', $subtree->node->tags->withoutInherited()->toStringArray()), ...$subtree->node->tags->onlyInherited()->toStringArray()]; + $explicitTags = $subtree->node->tags->withoutInherited()->toStringArray(); + sort($explicitTags); + $inheritedTags = $subtree->node->tags->onlyInherited()->toStringArray(); + sort($inheritedTags); + $tags = [...array_map(static fn(string $tag) => $tag . '*', $explicitTags), ...$inheritedTags]; } $result[] = str_repeat(' ', $subtree->level) . $subtree->node->nodeAggregateId->value . ($tags !== [] ? ' (' . implode(',', $tags) . ')' : ''); $subtreeStack = [...$subtree->children, ...$subtreeStack]; From f18931884c56d3c71519d53a1e16fca36c09965f Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sat, 4 May 2024 11:34:57 +0200 Subject: [PATCH 109/214] TASK Test handling of workspaces --- .../Features/ContentCache/ConvertUris.feature | 46 +++ .../NodesInOtherWorkspace.feature | 265 ++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInOtherWorkspace.feature diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature index a8f09b41c6b..89b7a07de97 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature @@ -31,6 +31,11 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-test" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-identifier" | And I am in the active content stream of workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | @@ -42,6 +47,9 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag | a | root | Neos.Neos:Site | {} | a | | a1 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1", "title": "Node a1"} | a1 | | a2 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a2", "title": "Node a2"} | a2 | + When the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-test" | And A site exists for node name "a" and domain "http://localhost" And the sites configuration is: """yaml @@ -124,3 +132,41 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag """ cacheVerifier=second execution, title=Node a2, link=Some value with node URI: /a1-new. """ + + Scenario: ContentCache doesn't get flushed when target node changes in different workspace + Given I have Fusion content cache enabled + And I am in the active content stream of workspace "live" and dimension space point {} + And the Fusion context node is a2 + + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a2, link=Some value with node URI: /a1. + """ + + And I am in the active content stream of workspace "user-test" and dimension space point {} + When the command SetNodeProperties is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "a1" | + | propertyValues | {"uriPathSegment": "a1-new"} | + And the graph projection is fully up to date + And The documenturipath projection is up to date + + And I am in the active content stream of workspace "live" and dimension space point {} + And the Fusion context node is a2 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a2, link=Some value with node URI: /a1. + """ diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInOtherWorkspace.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInOtherWorkspace.feature new file mode 100644 index 00000000000..5170073b739 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInOtherWorkspace.feature @@ -0,0 +1,265 @@ +@flowEntities +Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodetype specific tags not affecting different workspaces + + Background: + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.ContentRepository:Root': {} + 'Neos.Neos:Sites': + superTypes: + 'Neos.ContentRepository:Root': true + 'Neos.Neos:Document': + properties: + title: + type: string + uriPathSegment: + type: string + 'Neos.Neos:Site': + superTypes: + 'Neos.Neos:Document': true + 'Neos.Neos:Test.DocumentType1': + superTypes: + 'Neos.Neos:Document': true + 'Neos.Neos:Test.DocumentType2': + superTypes: + 'Neos.Neos:Document': true + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And I am user identified by "initiating-user-identifier" + + When the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | newContentStreamId | "cs-identifier" | + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-test" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-identifier" | + And I am in the active content stream of workspace "live" and dimension space point {} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "root" | + | nodeTypeName | "Neos.Neos:Sites" | + And the graph projection is fully up to date + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | + | a | root | Neos.Neos:Site | {} | site | + | a1 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1", "title": "Node a1"} | a1 | + | a1-1 | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1-1", "title": "Node a1-1"} | a1-1 | + | a2 | a | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a2", "title": "Node a2"} | a2 | + When the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-test" | + And A site exists for node name "a" and domain "http://localhost" + And the sites configuration is: + """yaml + Neos: + Neos: + sites: + '*': + contentRepository: default + contentDimensions: + resolver: + factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory + """ + And the Fusion context node is "a1" + And the Fusion context request URI is "http://localhost" + And I have the following Fusion setup: + """fusion + include: resource://Neos.Fusion/Private/Fusion/Root.fusion + include: resource://Neos.Neos/Private/Fusion/Root.fusion + + prototype(Neos.Neos:Test.DocumentType1) < prototype(Neos.Fusion:Component) { + + cacheVerifier = ${null} + title = ${q(node).property('title')} + + renderer = afx` + cacheVerifier={props.cacheVerifier}, + title={props.title} + ` + + @cache { + mode = 'cached' + entryIdentifier { + documentNode = ${Neos.Caching.entryIdentifierForNode(node)} + } + entryTags { + 1 = ${Neos.Caching.nodeTag(node)} + 2 = ${Neos.Caching.descendantOfTag(node)} + } + } + } + + prototype(Neos.Neos:Test.DocumentType2) < prototype(Neos.Fusion:Component) { + + cacheVerifier = ${null} + title = ${q(node).property('title')} + + renderer = afx` + cacheVerifier={props.cacheVerifier}, + title={props.title} + ` + + @cache { + mode = 'cached' + entryIdentifier { + documentNode = ${Neos.Caching.entryIdentifierForNode(node)} + } + entryTags { + 1 = ${Neos.Caching.nodeTag(node)} + 2 = ${Neos.Caching.nodeTypeTag('Neos.Neos:Document',node)} + } + } + } + + """ + + + Scenario: ContentCache doesn't get flushed when a property of a node in other workspace has changed + Given I have Fusion content cache enabled + And I am in the active content stream of workspace "live" and dimension space point {} + And the Fusion context node is a1 + + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a1 + """ + + And I am in the active content stream of workspace "user-test" and dimension space point {} + When the command SetNodeProperties is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "a1" | + | propertyValues | {"title": "Node a1 new"} | + And the graph projection is fully up to date + + And I am in the active content stream of workspace "live" and dimension space point {} + And the Fusion context node is a1 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a1 + """ + + Scenario: ContentCache gets not flushed when a property of another node has changed in different workspace + Given I have Fusion content cache enabled + And I am in the active content stream of workspace "live" and dimension space point {} + And the Fusion context node is a1 + + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a1 + """ + And I am in the active content stream of workspace "user-test" and dimension space point {} + When the command SetNodeProperties is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "a2" | + | propertyValues | {"title": "Node a2 new"} | + And the graph projection is fully up to date + + And I am in the active content stream of workspace "live" and dimension space point {} + And the Fusion context node is a1 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a1 + """ + + Scenario: ContentCache doesn't get flushed when a property of a node has changed by NodeType name in different workspace + Given I have Fusion content cache enabled + And I am in the active content stream of workspace "live" and dimension space point {} + And the Fusion context node is a2 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType2 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a2 + """ + + And I am in the active content stream of workspace "user-test" and dimension space point {} + When the command SetNodeProperties is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "a1" | + | propertyValues | {"title": "Node a1 new"} | + And the graph projection is fully up to date + + And I am in the active content stream of workspace "live" and dimension space point {} + And the Fusion context node is a2 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType2 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a2 + """ + + Scenario: ContentCache doesn't get flushed when a property of a node has changed of a descendant node in different workspace + Given I have Fusion content cache enabled + And I am in the active content stream of workspace "live" and dimension space point {} + And the Fusion context node is "a1" + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a1 + """ + + And I am in the active content stream of workspace "user-test" and dimension space point {} + When the command SetNodeProperties is executed with payload: + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "a1-1" | + | propertyValues | {"title": "Node a1-1 new"} | + And the graph projection is fully up to date + + And I am in the active content stream of workspace "live" and dimension space point {} + And the Fusion context node is "a1" + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, title=Node a1 + """ \ No newline at end of file From 2e8f97dd9c1023c1ec5e15000eb8bdb673dabf1f Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sat, 4 May 2024 17:33:36 +0200 Subject: [PATCH 110/214] TASK: Refactor ContentCacheFlusher to flush tags after persistence --- .../Classes/Fusion/Cache/ContentCacheFlusher.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index 1d104a0a5ff..a67e096ec16 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -50,7 +50,7 @@ class ContentCacheFlusher /** * @var array */ - private array $tagsToFlushOnShutdown = []; + private array $tagsToFlushAfterPersistance = []; public function __construct( protected readonly ContentCache $contentCache, @@ -302,15 +302,20 @@ public function registerAssetChange(AssetInterface $asset): void } } - $this->tagsToFlushOnShutdown = array_merge($tagsToFlush, $this->tagsToFlushOnShutdown); + $this->tagsToFlushAfterPersistance = array_merge($tagsToFlush, $this->tagsToFlushAfterPersistance); } /** * Flush caches according to the previously registered changes. */ + public function flushCollectedTags(): void + { + $this->flushTags($this->tagsToFlushAfterPersistance); + $this->tagsToFlushAfterPersistance = []; + } + public function shutdownObject(): void { - $this->flushTags($this->tagsToFlushOnShutdown); - $this->tagsToFlushOnShutdown = []; + $this->flushCollectedTags(); } } From 50d233694ca848ad5bd5d282cdeb759466fc32bb Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sat, 4 May 2024 17:34:30 +0200 Subject: [PATCH 111/214] TASK: Add tests for content cache flush on asset changes --- .../Features/Bootstrap/AssetTrait.php | 22 +- .../Features/Bootstrap/ContentCacheTrait.php | 39 +++ .../Features/Bootstrap/FeatureContext.php | 1 + .../Features/ContentCache/Assets.feature | 322 ++++++++++++++++++ 4 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 Neos.Neos/Tests/Behavior/Features/Bootstrap/ContentCacheTrait.php create mode 100644 Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/AssetTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/AssetTrait.php index 8998c61678c..ed9dfa5aa43 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/AssetTrait.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/AssetTrait.php @@ -20,8 +20,8 @@ /** * Step implementations for tests inside Neos.Neos * -* @internal only for behat tests within the Neos.Neos package -*/ + * @internal only for behat tests within the Neos.Neos package + */ trait AssetTrait { /** @@ -46,4 +46,22 @@ public function anAssetExistsWithId(string $assetId): void $this->getObject(AssetRepository::class)->add($asset); $this->getObject(PersistenceManagerInterface::class)->persistAll(); } + + /** + * @Given the asset :assetId has the title :title + * @Given the asset :assetId has the title :title and caption :caption + * @Given the asset :assetId has the title :title and caption :caption and copyright notice :copyrightNotice + */ + public function theAssetHasTheTitleAndCaptionAndCopyrightNotice($assetId, $title, $caption = null, $copyrightNotice = null): void + { + $repository = $this->getObject(AssetRepository::class); + $asset = $repository->findByIdentifier($assetId); + + $asset->setTitle($title); + $caption && $asset->setCaption($caption); + $copyrightNotice && $asset->setCopyrightNotice($copyrightNotice); + + $repository->update($asset); + $this->getObject(PersistenceManagerInterface::class)->persistAll(); + } } diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/ContentCacheTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/ContentCacheTrait.php new file mode 100644 index 00000000000..74686179a36 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/ContentCacheTrait.php @@ -0,0 +1,39 @@ + $className + * + * @return T + */ + abstract private function getObject(string $className): object; + + + /** + * @Given the ContentCacheFlusher flushes all collected tags + */ + public function theContentCacheFlusherFlushesAllCollectedTags(): void + { + $this->getObject(ContentCacheFlusher::class)->flushCollectedTags(); + } +} \ 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 340bff03d86..9a3be1acfbe 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php @@ -37,6 +37,7 @@ class FeatureContext implements BehatContext use MigrationsTrait; use FusionTrait; + use ContentCacheTrait; use AssetTrait; protected Environment $environment; diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature new file mode 100644 index 00000000000..0f6f904d969 --- /dev/null +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature @@ -0,0 +1,322 @@ +@flowEntities +Feature: Tests for the ContentCacheFlusher and cache flushing on asset changes + + Background: + Given using no content dimensions + And using the following node types: + """yaml + 'Neos.ContentRepository:Root': {} + 'Neos.Neos:Sites': + superTypes: + 'Neos.ContentRepository:Root': true + 'Neos.Neos:Document': + properties: + title: + type: string + uriPathSegment: + type: string + 'Neos.Neos:Site': + superTypes: + 'Neos.Neos:Document': true + 'Neos.Neos:Test.DocumentType1': + superTypes: + 'Neos.Neos:Document': true + properties: + asset: + type: Neos\Media\Domain\Model\Asset + assets: + type: array + 'Neos.Neos:Test.DocumentType2': + superTypes: + 'Neos.Neos:Document': true + properties: + text: + type: string + + """ + And using identifier "default", I define a content repository + And I am in content repository "default" + And I am user identified by "initiating-user-identifier" + + When an asset exists with id "an-asset-to-change" + And the asset "an-asset-to-change" has the title "First asset" and caption "This is an asset" and copyright notice "Copyright Neos 2024" + When an asset exists with id "some-other-asset" + And the asset "some-other-asset" has the title "Some other asset" and caption "This is some other asset" and copyright notice "Copyright Neos 2024" + And the ContentCacheFlusher flushes all collected tags + + When the command CreateRootWorkspace is executed with payload: + | Key | Value | + | workspaceName | "live" | + | newContentStreamId | "cs-identifier" | + And the command CreateWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-test" | + | baseWorkspaceName | "live" | + | newContentStreamId | "user-cs-identifier" | + And I am in the active content stream of workspace "live" and dimension space point {} + And the command CreateRootNodeAggregateWithNode is executed with payload: + | Key | Value | + | nodeAggregateId | "root" | + | nodeTypeName | "Neos.Neos:Sites" | + And the graph projection is fully up to date + And the following CreateNodeAggregateWithNode commands are executed: + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | + | a | root | Neos.Neos:Site | {} | site | + | a1 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1", "title": "Node a1"} | a1 | + | a1-1 | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1-1", "title": "Node a1-1"} | a1-1 | + | a1-2 | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1-1", "title": "Node a1-1"} | a1-2 | + | a2 | a | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a2", "title": "Node a2", "text": "Link to asset://an-asset-to-change."} | a2 | + | a3 | a | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a2", "title": "Node a2", "text": "Link to asset://some-other-asset."} | a3 | + When the command RebaseWorkspace is executed with payload: + | Key | Value | + | workspaceName | "user-test" | + And A site exists for node name "a" and domain "http://localhost" + And the sites configuration is: + """yaml + Neos: + Neos: + sites: + '*': + contentRepository: default + contentDimensions: + resolver: + factoryClassName: Neos\Neos\FrontendRouting\DimensionResolution\Resolver\NoopResolverFactory + """ + And the Fusion context node is "a1" + And the Fusion context request URI is "http://localhost" + And I have the following Fusion setup: + """fusion + include: resource://Neos.Fusion/Private/Fusion/Root.fusion + include: resource://Neos.Neos/Private/Fusion/Root.fusion + + prototype(Neos.Neos:Test.DocumentType1) < prototype(Neos.Fusion:Component) { + + cacheVerifier = ${null} + assetTitle = ${q(node).property("asset").title} + assetTitleOfArray = ${q(node).property("assets")[0].title} + + renderer = afx` + cacheVerifier={props.cacheVerifier}, + assetTitle={props.assetTitle}, + assetTitleOfArray={props.assetTitleOfArray} + ` + + @cache { + mode = 'cached' + entryIdentifier { + documentNode = ${Neos.Caching.entryIdentifierForNode(node)} + } + entryTags { + 1 = ${Neos.Caching.nodeTag(node)} + 2 = ${Neos.Caching.descendantOfTag(node)} + } + } + } + + prototype(Neos.Neos:Test.DocumentType2) < prototype(Neos.Fusion:Component) { + + cacheVerifier = ${null} + text = ${q(node).property('text')} + + renderer = afx` + cacheVerifier={props.cacheVerifier}, + text={props.text} + ` + + @cache { + mode = 'cached' + entryIdentifier { + documentNode = ${Neos.Caching.entryIdentifierForNode(node)} + } + entryTags { + 1 = ${Neos.Caching.nodeTag(node)} + 2 = ${Neos.Caching.descendantOfTag(node)} + } + } + } + + """ + + Scenario: ContentCache gets flushed when an referenced asset in a property has changed + Given I have Fusion content cache enabled + And the Fusion context node is "a1" + + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, assetTitle=First asset, assetTitleOfArray= + """ + + Then the asset "an-asset-to-change" has the title "First changed asset" + And the ContentCacheFlusher flushes all collected tags + + Then the Fusion context node is "a1" + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=second execution, assetTitle=First changed asset, assetTitleOfArray= + """ + + Scenario: ContentCache gets flushed when an referenced asset in a property array has changed + Given I have Fusion content cache enabled + And the Fusion context node is "a1-1" + + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, assetTitle=, assetTitleOfArray=First asset + """ + + Then the asset "an-asset-to-change" has the title "First changed asset" + And the ContentCacheFlusher flushes all collected tags + + Then the Fusion context node is "a1-1" + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=second execution, assetTitle=, assetTitleOfArray=First changed asset + """ + + + Scenario: ContentCache doesn't get flushed when another asset than the referenced asset in a property has changed + Given I have Fusion content cache enabled + And the Fusion context node is "a1-2" + + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, assetTitle=Some other asset, assetTitleOfArray= + """ + + Then the asset "an-asset-to-change" has the title "First changed asset" + And the ContentCacheFlusher flushes all collected tags + + Then the Fusion context node is "a1-2" + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType1 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, assetTitle=Some other asset, assetTitleOfArray= + """ + + Scenario: ContentCache gets flushed for live workspace when a referenced asset in a property text has changed + Given I have Fusion content cache enabled + And the Fusion context node is a2 + + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType2 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, text=Link to asset://an-asset-to-change. + """ + + Then the asset "an-asset-to-change" has the title "First changed asset" + And the ContentCacheFlusher flushes all collected tags + + Then the Fusion context node is a2 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType2 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=second execution, text=Link to asset://an-asset-to-change. + """ + + Scenario: ContentCache gets flushed for user workspace when a referenced asset in a property text has changed + Given I have Fusion content cache enabled + And I am in the active content stream of workspace "user-test" and dimension space point {} + And the Fusion context node is a2 + + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType2 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, text=Link to asset://an-asset-to-change. + """ + + And I am in the active content stream of workspace "live" and dimension space point {} + Then the asset "an-asset-to-change" has the title "First changed asset" + And the ContentCacheFlusher flushes all collected tags + + And I am in the active content stream of workspace "user-test" and dimension space point {} + Then the Fusion context node is a2 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType2 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=second execution, text=Link to asset://an-asset-to-change. + """ + + Scenario: ContentCache doesn't get flushed when a non-referenced asset in a property text has changed + Given I have Fusion content cache enabled + And the Fusion context node is a3 + + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType2 { + cacheVerifier = ${"first execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, text=Link to asset://some-other-asset. + """ + + Then the asset "an-asset-to-change" has the title "First changed asset" + And the ContentCacheFlusher flushes all collected tags + + Then the Fusion context node is a3 + And I execute the following Fusion code: + """fusion + test = Neos.Neos:Test.DocumentType2 { + cacheVerifier = ${"second execution"} + } + """ + Then I expect the following Fusion rendering result: + """ + cacheVerifier=first execution, text=Link to asset://some-other-asset. + """ From 097918f5bdc0842858909524321b7862be54b119 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sun, 5 May 2024 15:43:47 +0200 Subject: [PATCH 112/214] 4993 - Adjust subtree tag moval CTE to pass the tests --- .../Domain/Projection/Feature/NodeMove.php | 1 - .../Projection/Feature/SubtreeTagging.php | 33 +++++++++++++++---- .../Features/Bootstrap/ProjectedNodeTrait.php | 8 +++-- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php index fbd88417383..fdf61f7eceb 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php @@ -56,7 +56,6 @@ private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event): void ); $this->moveSubtreeTags( $event->contentStreamId, - $event->nodeAggregateId, $event->newParentNodeAggregateId, $succeedingSiblingForCoverage->dimensionSpacePoint ); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 56fd8440ffb..7e0cbc2564a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -134,8 +134,11 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void ]); } - private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId, NodeAggregateId $newParentNodeAggregateId, DimensionSpacePoint $coveredDimensionSpacePoint): void - { + private function moveSubtreeTags( + ContentStreamId $contentStreamId, + NodeAggregateId $newParentNodeAggregateId, + DimensionSpacePoint $coveredDimensionSpacePoint + ): void { $this->getDatabaseConnection()->executeStatement(' UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h, ( @@ -146,20 +149,36 @@ private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregate ' . $this->getTableNamePrefix() . '_hierarchyrelation th INNER JOIN ' . $this->getTableNamePrefix() . '_node tn ON tn.relationanchorpoint = th.childnodeanchor WHERE - tn.nodeaggregateid = :nodeAggregateId + tn.nodeaggregateid = :newParentNodeAggregateId AND th.contentstreamid = :contentStreamId AND th.dimensionspacepointhash = :dimensionSpacePointHash UNION - SELECT JSON_MERGE(cte.subtreetagsToInherit, JSON_KEYS(JSON_MERGE_PATCH(\'{}\', dh.subtreetags))) subtreeTagsToInherit, dh.childnodeanchor + SELECT + JSON_MERGE_PRESERVE( + cte.subtreeTagsToInherit, + JSON_KEYS(JSON_MERGE_PATCH( + \'{}\', + dh.subtreetags + )) + ) subtreeTagsToInherit, + dh.childnodeanchor FROM cte - JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.childnodeanchor + JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh + ON + dh.parentnodeanchor = cte.childnodeanchor + WHERE + dh.contentstreamid = :contentStreamId + AND dh.dimensionspacepointhash = :dimensionSpacePointHash ) SELECT * FROM cte ) AS r SET h.subtreetags = ( SELECT - JSON_MERGE_PATCH(JSON_OBJECTAGG(htk.k, null), JSON_MERGE_PATCH(\'{}\', h.subtreetags)) + JSON_MERGE_PATCH( + IFNULL(JSON_OBJECTAGG(htk.k, null), \'{}\'), + JSON_MERGE_PATCH(\'{}\', h.subtreetags) + ) FROM JSON_TABLE(r.subtreeTagsToInherit, \'$[*]\' COLUMNS (k VARCHAR(36) PATH \'$\')) htk ) @@ -169,7 +188,7 @@ private function moveSubtreeTags(ContentStreamId $contentStreamId, NodeAggregate AND h.dimensionspacepointhash = :dimensionSpacePointHash ', [ 'contentStreamId' => $contentStreamId->value, - 'nodeAggregateId' => $newParentNodeAggregateId->value, + 'newParentNodeAggregateId' => $newParentNodeAggregateId->value, 'dimensionSpacePointHash' => $coveredDimensionSpacePoint->hash, ]); } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index 05d64f2338e..5b6d14e9c82 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -248,9 +248,11 @@ public function iExpectTheNodeWithAggregateIdentifierToNotContainTheTag(string $ public function iExpectThisNodeToBeExactlyExplicitlyTagged(string $expectedTagList): void { $this->assertOnCurrentNode(function (Node $currentNode) use ($expectedTagList) { + $actualTags = $currentNode->tags->withoutInherited()->toStringArray(); + sort($actualTags); Assert::assertSame( ($expectedTagList === '') ? [] : explode(',', $expectedTagList), - $currentNode->tags->withoutInherited()->toStringArray() + $actualTags ); }); } @@ -262,9 +264,11 @@ public function iExpectThisNodeToBeExactlyExplicitlyTagged(string $expectedTagLi public function iExpectThisNodeToExactlyInheritTheTags(string $expectedTagList): void { $this->assertOnCurrentNode(function (Node $currentNode) use ($expectedTagList) { + $actualTags = $currentNode->tags->onlyInherited()->toStringArray(); + sort($actualTags); Assert::assertSame( ($expectedTagList === '') ? [] : explode(',', $expectedTagList), - $currentNode->tags->onlyInherited()->toStringArray(), + $actualTags, ); }); } From 27f07e35347656e8983fec2f18d3a9b3080b4dc1 Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sun, 5 May 2024 17:20:40 +0200 Subject: [PATCH 113/214] TASK: Update test features, refactoring for asset handling in Neos test suite --- .../Features/Bootstrap/FeatureContext.php | 31 ++++++++++++++++++- .../Features/ContentCache/Assets.feature | 24 +++++++------- .../Features/ContentCache/ConvertUris.feature | 30 +++++++++--------- .../Features/ContentCache/Nodes.feature | 4 +-- 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php index 9a3be1acfbe..f8b699afcf7 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php @@ -24,6 +24,8 @@ use Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap\MigrationsTrait; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Utility\Environment; +use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite; +use Neos\Flow\Persistence\PersistenceManagerInterface; class FeatureContext implements BehatContext { @@ -31,7 +33,9 @@ class FeatureContext implements BehatContext use FlowEntitiesTrait; use BrowserTrait; - use CRTestSuiteTrait; + use CRTestSuiteTrait { + deserializeProperties as deserializePropertiesCrTestSuiteTrait; + } use CRBehavioralTestsSubjectProvider; use RoutingTrait; use MigrationsTrait; @@ -43,12 +47,14 @@ class FeatureContext implements BehatContext protected Environment $environment; protected ContentRepositoryRegistry $contentRepositoryRegistry; + protected PersistenceManagerInterface $persistenceManager; public function __construct() { self::bootstrapFlow(); $this->environment = $this->getObject(Environment::class); $this->contentRepositoryRegistry = $this->getObject(ContentRepositoryRegistry::class); + $this->persistenceManager = $this->getObject(PersistenceManagerInterface::class); $this->setupCRTestSuiteTrait(); } @@ -103,4 +109,27 @@ protected function createContentRepository( return $contentRepository; } + + protected function deserializeProperties(array $properties): PropertyValuesToWrite + { + $properties = array_map( + $this->loadFlowObjectsRecursive(...), + $properties + ); + + return $this->deserializePropertiesCrTestSuiteTrait($properties); + } + + public function loadFlowObjectsRecursive(mixed $value): mixed + { + if (is_array($value) && isset($value['__flow_object_type'])) { + return $this->persistenceManager->getObjectByIdentifier($value['__identifier'], $value['__flow_object_type'], true); + } elseif (is_array($value)) { + return array_map( + $this->loadFlowObjectsRecursive(...), + $value + ); + } + return $value; + } } diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature index 0f6f904d969..583ba992e7c 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature @@ -49,9 +49,9 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on asset changes | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the command CreateWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-test" | - | baseWorkspaceName | "live" | + | Key | Value | + | workspaceName | "user-test" | + | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-identifier" | And I am in the active content stream of workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: @@ -60,16 +60,16 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on asset changes | nodeTypeName | "Neos.Neos:Sites" | And the graph projection is fully up to date And the following CreateNodeAggregateWithNode commands are executed: - | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | - | a | root | Neos.Neos:Site | {} | site | - | a1 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1", "title": "Node a1"} | a1 | - | a1-1 | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1-1", "title": "Node a1-1"} | a1-1 | - | a1-2 | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1-1", "title": "Node a1-1"} | a1-2 | - | a2 | a | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a2", "title": "Node a2", "text": "Link to asset://an-asset-to-change."} | a2 | - | a3 | a | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a2", "title": "Node a2", "text": "Link to asset://some-other-asset."} | a3 | + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | + | a | root | Neos.Neos:Site | {} | site | + | a1 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1", "title": "Node a1", "asset": {"__flow_object_type":"Neos\\Media\\Domain\\Model\\Asset","__identifier":"an-asset-to-change"}} | a1 | + | a1-1 | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1-1", "title": "Node a1-1", "assets": [{"__flow_object_type":"Neos\\Media\\Domain\\Model\\Asset","__identifier":"an-asset-to-change"}]} | a1-1 | + | a1-2 | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1-2", "title": "Node a1-2", "asset": {"__flow_object_type":"Neos\\Media\\Domain\\Model\\Asset","__identifier":"some-other-asset"}} | a1-2 | + | a2 | a | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a2", "title": "Node a2", "text": "Link to asset://an-asset-to-change."} | a2 | + | a3 | a | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a2", "title": "Node a2", "text": "Link to asset://some-other-asset."} | a3 | When the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-test" | + | Key | Value | + | workspaceName | "user-test" | And A site exists for node name "a" and domain "http://localhost" And the sites configuration is: """yaml diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature index 89b7a07de97..15142b1045a 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature @@ -32,9 +32,9 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the command CreateWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-test" | - | baseWorkspaceName | "live" | + | Key | Value | + | workspaceName | "user-test" | + | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-identifier" | And I am in the active content stream of workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: @@ -43,13 +43,13 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag | nodeTypeName | "Neos.Neos:Sites" | And the graph projection is fully up to date And the following CreateNodeAggregateWithNode commands are executed: - | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | - | a | root | Neos.Neos:Site | {} | a | - | a1 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1", "title": "Node a1"} | a1 | - | a2 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a2", "title": "Node a2"} | a2 | + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | + | a | root | Neos.Neos:Site | {} | a | + | a1 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1", "title": "Node a1"} | a1 | + | a2 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a2", "title": "Node a2"} | a2 | When the command RebaseWorkspace is executed with payload: - | Key | Value | - | workspaceName | "user-test" | + | Key | Value | + | workspaceName | "user-test" | And A site exists for node name "a" and domain "http://localhost" And the sites configuration is: """yaml @@ -114,9 +114,9 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag """ When the command SetNodeProperties is executed with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "a1" | + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "a1" | | propertyValues | {"uriPathSegment": "a1-new"} | And the graph projection is fully up to date And The documenturipath projection is up to date @@ -151,9 +151,9 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag And I am in the active content stream of workspace "user-test" and dimension space point {} When the command SetNodeProperties is executed with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | - | nodeAggregateId | "a1" | + | Key | Value | + | contentStreamId | "cs-identifier" | + | nodeAggregateId | "a1" | | propertyValues | {"uriPathSegment": "a1-new"} | And the graph projection is fully up to date And The documenturipath projection is up to date diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature index f8eacea222b..e3a2b22fecd 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature @@ -227,8 +227,8 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety """ When the command SetNodeProperties is executed with payload: - | Key | Value | - | contentStreamId | "cs-identifier" | + | Key | Value | + | contentStreamId | "cs-identifier" | | nodeAggregateId | "a1-1" | | propertyValues | {"title": "Node a1-1 new"} | And the graph projection is fully up to date From 6616d3083b7730385025da40cefd2fbe3e6b47ee Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Sun, 5 May 2024 17:32:30 +0200 Subject: [PATCH 114/214] TASK: Update test features, refactoring for asset handling in Neos test suite --- .../Tests/Behavior/Features/Bootstrap/ContentCacheTrait.php | 2 +- Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature | 2 +- .../Features/ContentCache/NodesInOtherWorkspace.feature | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/ContentCacheTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/ContentCacheTrait.php index 74686179a36..193accfbf5b 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/ContentCacheTrait.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/ContentCacheTrait.php @@ -36,4 +36,4 @@ public function theContentCacheFlusherFlushesAllCollectedTags(): void { $this->getObject(ContentCacheFlusher::class)->flushCollectedTags(); } -} \ No newline at end of file +} diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature index e3a2b22fecd..c058092286a 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature @@ -243,4 +243,4 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety Then I expect the following Fusion rendering result: """ cacheVerifier=second execution, title=Node a1 - """ \ No newline at end of file + """ diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInOtherWorkspace.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInOtherWorkspace.feature index 5170073b739..457b9d716b3 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInOtherWorkspace.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInOtherWorkspace.feature @@ -262,4 +262,4 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety Then I expect the following Fusion rendering result: """ cacheVerifier=first execution, title=Node a1 - """ \ No newline at end of file + """ From 7d691d85394c9819b64996c51cc208f321603212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Mon, 6 May 2024 09:31:29 +0200 Subject: [PATCH 115/214] TASK: ContentGraph bound to Workspace The ContentGraph is now always bound to a `WorkspaceName` and `ContentStreamId` combination. The ProjectionState of the ContentGraph is now the `ContentGraphFinder` which allows you to get an instance of the `ContentGraph` bound to a combination (usually by giving only the `WorkspaceName`). The userland way is to use `ContentRepository::getContentGraph(WorkspaceName)` --- .../src/ContentGraphFactory.php | 64 ++++ .../src/ContentGraphTableNames.php | 44 +++ .../DoctrineDbalContentGraphProjection.php | 110 +++--- ...trineDbalContentGraphProjectionFactory.php | 27 +- .../DoctrineDbalContentGraphSchemaBuilder.php | 13 +- .../src/Domain/Repository/ContentGraph.php | 239 +++++-------- .../src/Domain/Repository/ContentSubgraph.php | 335 ++++-------------- .../DimensionSpacePointsRepository.php | 10 +- .../src/NodeQueryBuilder.php | 322 +++++++++++++++++ .../src/ContentHyperGraphFactory.php | 62 ++++ .../Projection/HypergraphProjection.php | 29 +- .../Domain/Repository/ContentHypergraph.php | 45 +-- .../src/HypergraphProjectionFactory.php | 17 +- ...SetNodeReferences_ConstraintChecks.feature | 2 +- .../Classes/ContentGraphFactoryInterface.php | 31 ++ .../Classes/ContentGraphFinder.php | 106 ++++++ .../Classes/ContentGraphFinderInterface.php | 36 ++ .../Classes/ContentRepository.php | 5 +- .../Factory/ContentRepositoryFactory.php | 12 +- ...ntRepositoryServiceFactoryDependencies.php | 6 +- .../Feature/Common/ConstraintChecks.php | 134 +++---- .../Common/ContentStreamIdOverride.php | 67 ---- .../Feature/Common/NodeCreationInternals.php | 24 +- .../Feature/Common/NodeVariationInternals.php | 131 +++---- .../Feature/Common/TetheredNodeInternals.php | 19 +- .../Feature/ContentStreamCommandHandler.php | 40 +-- .../DimensionSpaceCommandHandler.php | 59 +-- .../Feature/NodeAggregateCommandHandler.php | 45 +-- .../Feature/NodeCreation/NodeCreation.php | 64 ++-- .../Feature/NodeDisabling/NodeDisabling.php | 35 +- .../NodeDuplicationCommandHandler.php | 62 ++-- .../NodeModification/NodeModification.php | 32 +- .../Classes/Feature/NodeMove/NodeMove.php | 60 ++-- .../NodeReferencing/NodeReferencing.php | 33 +- .../Command/RemoveNodeAggregate.php | 1 - .../Feature/NodeRemoval/NodeRemoval.php | 19 +- .../Feature/NodeRenaming/NodeRenaming.php | 20 +- .../Feature/NodeTypeChange/NodeTypeChange.php | 117 +++--- .../Command/CreateNodeVariant.php | 1 - .../Feature/NodeVariation/NodeVariation.php | 22 +- .../RootNodeCreation/RootNodeHandling.php | 44 ++- .../Feature/SubtreeTagging/SubtreeTagging.php | 19 +- .../Feature/WorkspaceCommandHandler.php | 173 +++++---- .../ContentGraph/ContentGraphInterface.php | 18 +- .../ContentGraph/ContentGraphProjection.php | 7 +- .../ContentGraph/ContentSubgraphInterface.php | 2 +- .../ContentStream/ContentStreamFinder.php | 17 - .../Service/ContentRepositoryBootstrapper.php | 4 +- .../Behavior/Bootstrap/FeatureContext.php | 11 +- .../src/NodeMigrationService.php | 12 +- .../DisallowedChildNodeAdjustment.php | 6 +- .../src/Adjustment/ProjectedNodeIterator.php | 22 +- .../Adjustment/TetheredNodeAdjustments.php | 34 +- .../src/StructureAdjustmentService.php | 9 +- .../src/StructureAdjustmentServiceFactory.php | 2 +- .../Bootstrap/CRTestSuiteRuntimeVariables.php | 10 +- .../Features/Bootstrap/CRTestSuiteTrait.php | 5 +- .../Bootstrap/Features/NodeCopying.php | 3 +- .../Bootstrap/ProjectedNodeAggregateTrait.php | 16 +- .../Features/Bootstrap/ProjectedNodeTrait.php | 13 +- .../Command/ContentCommandController.php | 10 +- .../Classes/ContentRepositoryRegistry.php | 11 +- .../Classes/Controller/UsageController.php | 9 +- .../Dto/AssetIdAndOriginalAssetId.php | 1 + .../Service/AssetUsageSyncService.php | 15 +- .../Service/AssetUsageSyncServiceFactory.php | 4 +- .../Controller/Backend/ContentController.php | 3 +- .../Controller/Frontend/NodeController.php | 6 +- .../Module/Administration/SitesController.php | 6 +- .../Management/WorkspacesController.php | 24 +- .../Controller/Service/NodesController.php | 32 +- .../Domain/Service/SiteNodeUtility.php | 10 +- .../Domain/Service/SiteServiceInternals.php | 11 +- .../Service/SiteServiceInternalsFactory.php | 2 +- .../Classes/Domain/Workspace/Workspace.php | 6 +- .../Fusion/Cache/ContentCacheFlusher.php | 17 +- ...phProjectorCatchUpHookForCacheFlushing.php | 20 +- .../Cache/NeosFusionContextSerializer.php | 3 +- .../DimensionsMenuItemsImplementation.php | 20 +- .../Classes/Fusion/Helper/DimensionHelper.php | 8 +- Neos.Neos/Classes/Service/LinkingService.php | 3 +- .../HackyNodeAddressToNodeConverter.php | 3 +- .../Classes/View/FusionExceptionView.php | 2 +- .../ViewHelpers/Link/NodeViewHelper.php | 6 +- .../ViewHelpers/Uri/NodeViewHelper.php | 3 +- .../Features/Bootstrap/BrowserTrait.php | 4 +- .../Service/TimeableNodeVisibilityService.php | 5 +- .../Behavior/Bootstrap/FeatureContext.php | 6 +- 88 files changed, 1673 insertions(+), 1504 deletions(-) create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php create mode 100644 Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php create mode 100644 Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php create mode 100644 Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php create mode 100644 Neos.ContentRepository.Core/Classes/ContentGraphFinder.php create mode 100644 Neos.ContentRepository.Core/Classes/ContentGraphFinderInterface.php delete mode 100644 Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php new file mode 100644 index 00000000000..e4825f6cf0c --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php @@ -0,0 +1,64 @@ +contentRepositoryId->value, + 'Workspace' + )); + + $row = $this->client->getConnection()->executeQuery( + ' + SELECT * FROM ' . $tableName . ' + WHERE workspaceName = :workspaceName + ', + [ + 'workspaceName' => $workspaceName->value, + ] + )->fetchAssociative(); + + if ($row === false) { + throw new ContentStreamDoesNotExistYet('The workspace "' . $workspaceName->value . '" does not exist.', 1714839710); + } + + return $this->buildForWorkspaceAndContentStream($workspaceName, ContentStreamId::fromString($row['currentcontentstreamid'])); + } + + public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraph + { + return new ContentGraph($this->client, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNamePrefix, $workspaceName, $contentStreamId); + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php new file mode 100644 index 00000000000..f810e070d6f --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -0,0 +1,44 @@ +tableNamePrefix . '_node'; + } + + public function hierachyRelation(): string + { + return $this->tableNamePrefix . '_hierarchyrelation'; + } + + public function dimensionSpacePoints(): string + { + return $this->tableNamePrefix . '_dimensionspacepoints'; + } + + public function referenceRelation(): string + { + return $this->tableNamePrefix . '_referencerelation'; + } + + public function checkpoint(): string + { + return $this->tableNamePrefix . '_checkpoint'; + } +} diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index a3a92150aa8..0e5ad53cd74 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -16,8 +16,8 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentGraph; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; @@ -47,7 +47,6 @@ use Neos\ContentRepository\Core\Infrastructure\DbalCheckpointStorage; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; use Neos\ContentRepository\Core\Infrastructure\DbalSchemaDiff; -use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\CheckpointStorageStatusType; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTags; @@ -55,7 +54,6 @@ use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionStatus; use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; -use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; @@ -64,7 +62,7 @@ use Neos\EventStore\Model\EventEnvelope; /** - * @implements ProjectionInterface + * @implements ProjectionInterface * @internal but the graph projection is api */ final class DoctrineDbalContentGraphProjection implements ProjectionInterface, WithMarkStaleInterface @@ -77,26 +75,21 @@ final class DoctrineDbalContentGraphProjection implements ProjectionInterface, W public const RELATION_DEFAULT_OFFSET = 128; - /** - * @var ContentGraph|null Cache for the content graph returned by {@see getState()}, - * so that always the same instance is returned - */ - private ?ContentGraph $contentGraph = null; - private DbalCheckpointStorage $checkpointStorage; + private ContentGraphTableNames $contentGraphTableNames; + public function __construct( private readonly DbalClientInterface $dbalClient, - private readonly NodeFactory $nodeFactory, - private readonly ContentRepositoryId $contentRepositoryId, - private readonly NodeTypeManager $nodeTypeManager, private readonly ProjectionContentGraph $projectionContentGraph, private readonly string $tableNamePrefix, - private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository + private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository, + private readonly ContentGraphFinder $contentGraphFinder ) { + $this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); $this->checkpointStorage = new DbalCheckpointStorage( $this->dbalClient->getConnection(), - $this->tableNamePrefix . '_checkpoint', + $this->contentGraphTableNames->checkpoint(), self::class ); } @@ -165,19 +158,22 @@ public function reset(): void $this->checkpointStorage->acquireLock(); $this->checkpointStorage->updateAndReleaseLock(SequenceNumber::none()); - $contentGraph = $this->getState(); - foreach ($contentGraph->getSubgraphs() as $subgraph) { - $subgraph->inMemoryCache->enable(); + $contentGraphFinder = $this->getState(); + /** @var ContentGraph $contentGraph */ + foreach ($contentGraphFinder->getInstances() as $contentGraph) { + foreach ($contentGraph->getSubgraphs() as $subgraph) { + $subgraph->inMemoryCache->enable(); + } } } private function truncateDatabaseTables(): void { $connection = $this->dbalClient->getConnection(); - $connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_node'); - $connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_hierarchyrelation'); - $connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_referencerelation'); - $connection->executeQuery('TRUNCATE table ' . $this->tableNamePrefix . '_dimensionspacepoints'); + $connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->node()); + $connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->hierachyRelation()); + $connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->referenceRelation()); + $connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->dimensionSpacePoints()); } public function canHandle(EventInterface $event): bool @@ -234,25 +230,19 @@ public function getCheckpointStorage(): DbalCheckpointStorage return $this->checkpointStorage; } - public function getState(): ContentGraph + public function getState(): ContentGraphFinder { - if (!$this->contentGraph) { - $this->contentGraph = new ContentGraph( - $this->dbalClient, - $this->nodeFactory, - $this->contentRepositoryId, - $this->nodeTypeManager, - $this->tableNamePrefix - ); - } - return $this->contentGraph; + return $this->contentGraphFinder; } public function markStale(): void { - $contentGraph = $this->getState(); - foreach ($contentGraph->getSubgraphs() as $subgraph) { - $subgraph->inMemoryCache->disable(); + $contentGraphFinder = $this->getState(); + /** @var ContentGraph $contentGraph */ + foreach ($contentGraphFinder->getInstances() as $contentGraph) { + foreach ($contentGraph->getSubgraphs() as $subgraph) { + $subgraph->inMemoryCache->disable(); + } } } @@ -311,7 +301,7 @@ private function whenRootNodeAggregateDimensionsWereUpdated(RootNodeAggregateDim $this->transactional(function () use ($rootNodeAnchorPoint, $event) { // delete all hierarchy edges of the root node $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->tableNamePrefix . '_hierarchyrelation + DELETE FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' WHERE parentnodeanchor = :parentNodeAnchor AND childnodeanchor = :childNodeAnchor @@ -360,8 +350,8 @@ private function whenNodeAggregateNameWasChanged(NodeAggregateNameWasChanged $ev { $this->transactional(function () use ($event, $eventEnvelope) { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h - INNER JOIN ' . $this->tableNamePrefix . '_node n on + UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h + INNER JOIN ' . $this->contentGraphTableNames->node() . ' n on h.childnodeanchor = n.relationanchorpoint SET h.name = :newName, @@ -597,7 +587,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void // 1) Copy HIERARCHY RELATIONS (this is the MAIN OPERATION here) // $this->getDatabaseConnection()->executeUpdate(' - INSERT INTO ' . $this->tableNamePrefix . '_hierarchyrelation ( + INSERT INTO ' . $this->contentGraphTableNames->hierachyRelation() . ' ( parentnodeanchor, childnodeanchor, `name`, @@ -615,7 +605,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void h.subtreetags, "' . $event->newContentStreamId->value . '" AS contentstreamid FROM - ' . $this->tableNamePrefix . '_hierarchyrelation h + ' . $this->contentGraphTableNames->hierachyRelation() . ' h WHERE h.contentstreamid = :sourceContentStreamId ', [ 'sourceContentStreamId' => $event->sourceContentStreamId->value @@ -632,7 +622,7 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo // Drop hierarchy relations $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->tableNamePrefix . '_hierarchyrelation + DELETE FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' WHERE contentstreamid = :contentStreamId ', [ @@ -641,23 +631,23 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo // Drop non-referenced nodes (which do not have a hierarchy relation anymore) $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->tableNamePrefix . '_node + DELETE FROM ' . $this->contentGraphTableNames->node() . ' WHERE NOT EXISTS ( - SELECT 1 FROM ' . $this->tableNamePrefix . '_hierarchyrelation - WHERE ' . $this->tableNamePrefix . '_hierarchyrelation.childnodeanchor - = ' . $this->tableNamePrefix . '_node.relationanchorpoint + SELECT 1 FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' + WHERE ' . $this->contentGraphTableNames->hierachyRelation() . '.childnodeanchor + = ' . $this->contentGraphTableNames->node() . '.relationanchorpoint ) '); // Drop non-referenced reference relations (i.e. because the referenced nodes are gone by now) $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->tableNamePrefix . '_referencerelation + DELETE FROM ' . $this->contentGraphTableNames->referenceRelation() . ' WHERE NOT EXISTS ( - SELECT 1 FROM ' . $this->tableNamePrefix . '_node - WHERE ' . $this->tableNamePrefix . '_node.relationanchorpoint - = ' . $this->tableNamePrefix . '_referencerelation.nodeanchorpoint + SELECT 1 FROM ' . $this->contentGraphTableNames->node() . ' + WHERE ' . $this->contentGraphTableNames->node() . '.relationanchorpoint + = ' . $this->contentGraphTableNames->referenceRelation() . '.nodeanchorpoint ) '); }); @@ -742,7 +732,7 @@ function (NodeRecord $node) use ($eventEnvelope) { ); // remove old - $this->getDatabaseConnection()->delete($this->tableNamePrefix . '_referencerelation', [ + $this->getDatabaseConnection()->delete($this->contentGraphTableNames->referenceRelation(), [ 'nodeanchorpoint' => $nodeAnchorPoint?->value, 'name' => $event->referenceName->value ]); @@ -751,7 +741,7 @@ function (NodeRecord $node) use ($eventEnvelope) { $position = 0; /** @var SerializedNodeReference $reference */ foreach ($event->references as $reference) { - $this->getDatabaseConnection()->insert($this->tableNamePrefix . '_referencerelation', [ + $this->getDatabaseConnection()->insert($this->contentGraphTableNames->referenceRelation(), [ 'name' => $event->referenceName->value, 'position' => $position, 'nodeanchorpoint' => $nodeAnchorPoint?->value, @@ -870,7 +860,7 @@ private function updateNodeRecordWithCopyOnWrite( // IMPORTANT: We need to reconnect BOTH the incoming and outgoing edges. $this->getDatabaseConnection()->executeStatement( ' - UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h + UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h SET -- if our (copied) node is the child, we update h.childNodeAnchor h.childnodeanchor @@ -914,7 +904,7 @@ private function copyReferenceRelations( NodeRelationAnchorPoint $destinationRelationAnchorPoint ): void { $this->getDatabaseConnection()->executeStatement(' - INSERT INTO ' . $this->tableNamePrefix . '_referencerelation ( + INSERT INTO ' . $this->contentGraphTableNames->referenceRelation() . ' ( nodeanchorpoint, name, position, @@ -926,7 +916,7 @@ private function copyReferenceRelations( ref.position, ref.destinationnodeaggregateid FROM - ' . $this->tableNamePrefix . '_referencerelation ref + ' . $this->contentGraphTableNames->referenceRelation() . ' ref WHERE ref.nodeanchorpoint = :sourceNodeAnchorPoint ', [ 'sourceNodeAnchorPoint' => $sourceRelationAnchorPoint->value, @@ -945,8 +935,8 @@ private function whenDimensionSpacePointWasMoved(DimensionSpacePointWasMoved $ev // 1) originDimensionSpacePoint on Node $rel = $this->getDatabaseConnection()->executeQuery( 'SELECT n.relationanchorpoint, n.origindimensionspacepointhash - FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + FROM ' . $this->contentGraphTableNames->node() . ' n + INNER JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint AND h.contentstreamid = :contentStreamId @@ -975,7 +965,7 @@ function (NodeRecord $nodeRecord) use ($event) { // 2) hierarchy relations $this->getDatabaseConnection()->executeStatement( ' - UPDATE ' . $this->tableNamePrefix . '_hierarchyrelation h + UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h SET h.dimensionspacepointhash = :newDimensionSpacePointHash WHERE @@ -999,7 +989,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded // 1) hierarchy relations $this->getDatabaseConnection()->executeStatement( ' - INSERT INTO ' . $this->tableNamePrefix . '_hierarchyrelation ( + INSERT INTO ' . $this->contentGraphTableNames->hierachyRelation() . ' ( parentnodeanchor, childnodeanchor, `name`, @@ -1017,7 +1007,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded :newDimensionSpacePointHash AS dimensionspacepointhash, h.contentstreamid FROM - ' . $this->tableNamePrefix . '_hierarchyrelation h + ' . $this->contentGraphTableNames->hierachyRelation() . ' h WHERE h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :sourceDimensionSpacePointHash', [ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php index 64891e2cc33..4d24c49a34a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php @@ -7,6 +7,7 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphProjection; @@ -43,23 +44,31 @@ public function build( $dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbalClient->getConnection(), $tableNamePrefix); + $nodeFactory = new NodeFactory( + $projectionFactoryDependencies->contentRepositoryId, + $projectionFactoryDependencies->nodeTypeManager, + $projectionFactoryDependencies->propertyConverter, + $dimensionSpacePointsRepository + ); + + $contentGraphFactory = new ContentGraphFactory( + $this->dbalClient, + $nodeFactory, + $projectionFactoryDependencies->contentRepositoryId, + $projectionFactoryDependencies->nodeTypeManager, + $tableNamePrefix + ); + return new ContentGraphProjection( new DoctrineDbalContentGraphProjection( $this->dbalClient, - new NodeFactory( - $projectionFactoryDependencies->contentRepositoryId, - $projectionFactoryDependencies->nodeTypeManager, - $projectionFactoryDependencies->propertyConverter, - $dimensionSpacePointsRepository - ), - $projectionFactoryDependencies->contentRepositoryId, - $projectionFactoryDependencies->nodeTypeManager, new ProjectionContentGraph( $this->dbalClient, $tableNamePrefix ), $tableNamePrefix, - $dimensionSpacePointsRepository + $dimensionSpacePointsRepository, + new ContentGraphFinder($contentGraphFactory) ) ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index 7722ff41f6f..c73be9afd58 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -17,9 +17,12 @@ class DoctrineDbalContentGraphSchemaBuilder { private const DEFAULT_TEXT_COLLATION = 'utf8mb4_unicode_520_ci'; + private readonly ContentGraphTableNames $contentGraphTableNames; + public function __construct( - private readonly string $tableNamePrefix + string $tableNamePrefix ) { + $this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); } public function buildSchema(AbstractSchemaManager $schemaManager): Schema @@ -34,7 +37,7 @@ public function buildSchema(AbstractSchemaManager $schemaManager): Schema private function createNodeTable(): Table { - $table = new Table($this->tableNamePrefix . '_node', [ + $table = new Table($this->contentGraphTableNames->node(), [ DbalSchemaFactory::columnForNodeAnchorPoint('relationanchorpoint')->setAutoincrement(true), DbalSchemaFactory::columnForNodeAggregateId('nodeaggregateid')->setNotnull(false), DbalSchemaFactory::columnForDimensionSpacePointHash('origindimensionspacepointhash')->setNotnull(false), @@ -55,7 +58,7 @@ private function createNodeTable(): Table private function createHierarchyRelationTable(): Table { - $table = new Table($this->tableNamePrefix . '_hierarchyrelation', [ + $table = new Table($this->contentGraphTableNames->hierachyRelation(), [ (new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('charset', 'ascii')->setCustomSchemaOption('collation', 'ascii_general_ci'), (new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotnull(true), @@ -75,7 +78,7 @@ private function createHierarchyRelationTable(): Table private function createDimensionSpacePointsTable(): Table { - $table = new Table($this->tableNamePrefix . '_dimensionspacepoints', [ + $table = new Table($this->contentGraphTableNames->dimensionSpacePoints(), [ DbalSchemaFactory::columnForDimensionSpacePointHash('hash')->setNotnull(true), DbalSchemaFactory::columnForDimensionSpacePoint('dimensionspacepoint')->setNotnull(true) ]); @@ -86,7 +89,7 @@ private function createDimensionSpacePointsTable(): Table private function createReferenceRelationTable(): Table { - $table = new Table($this->tableNamePrefix . '_referencerelation', [ + $table = new Table($this->contentGraphTableNames->referenceRelation(), [ (new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(true)->setCustomSchemaOption('charset', 'ascii')->setCustomSchemaOption('collation', 'ascii_general_ci'), (new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForNodeAnchorPoint('nodeanchorpoint'), diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 4781ae1a82e..23f2e9040ae 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -20,7 +20,7 @@ use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; +use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; @@ -40,6 +40,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * The Doctrine DBAL adapter content graph @@ -64,6 +65,8 @@ */ final class ContentGraph implements ContentGraphInterface { + private readonly NodeQueryBuilder $nodeQueryBuilder; + /** * @var array */ @@ -74,21 +77,24 @@ public function __construct( private readonly NodeFactory $nodeFactory, private readonly ContentRepositoryId $contentRepositoryId, private readonly NodeTypeManager $nodeTypeManager, - private readonly string $tableNamePrefix + private readonly string $tableNamePrefix, + public readonly WorkspaceName $workspaceName, + public readonly ContentStreamId $contentStreamId ) { + $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $this->tableNamePrefix); } - final public function getSubgraph( - ContentStreamId $contentStreamId, + public function getSubgraph( DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints ): ContentSubgraphInterface { - $index = $contentStreamId->value . '-' . $dimensionSpacePoint->hash . '-' . $visibilityConstraints->getHash(); + $index = $dimensionSpacePoint->hash . '-' . $visibilityConstraints->getHash(); if (!isset($this->subgraphs[$index])) { $this->subgraphs[$index] = new ContentSubgraphWithRuntimeCaches( new ContentSubgraph( $this->contentRepositoryId, - $contentStreamId, + $this->workspaceName, + $this->contentStreamId, $dimensionSpacePoint, $visibilityConstraints, $this->client, @@ -106,11 +112,9 @@ final public function getSubgraph( * @throws RootNodeAggregateDoesNotExist */ public function findRootNodeAggregateByType( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): NodeAggregate { $rootNodeAggregates = $this->findRootNodeAggregates( - $contentStreamId, FindRootNodeAggregatesFilter::create(nodeTypeName: $nodeTypeName) ); @@ -126,76 +130,47 @@ public function findRootNodeAggregateByType( )); } - $rootNodeAggregate = $rootNodeAggregates->first(); - - if ($rootNodeAggregate === null) { + if (!$rootNodeAggregates->first()) { throw RootNodeAggregateDoesNotExist::butWasExpectedTo($nodeTypeName); } - return $rootNodeAggregate; + return $rootNodeAggregates->first(); } public function findRootNodeAggregates( - ContentStreamId $contentStreamId, FindRootNodeAggregatesFilter $filter, ): NodeAggregates { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->where('h.contentstreamid = :contentStreamId') - ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') - ->setParameters([ - 'contentStreamId' => $contentStreamId->value, - 'rootEdgeParentAnchorId' => NodeRelationAnchorPoint::forRootEdge()->value, - ]); - - if ($filter->nodeTypeName !== null) { - $queryBuilder - ->andWhere('n.nodetypename = :nodeTypeName') - ->setParameter('nodeTypeName', $filter->nodeTypeName->value); - } - return NodeAggregates::fromArray(iterator_to_array($this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId))); + $rootNodeAggregateQueryBuilder = $this->nodeQueryBuilder->buildFindRootNodeAggregatesQuery($this->contentStreamId, $filter); + return NodeAggregates::fromArray(iterator_to_array($this->mapQueryBuilderToNodeAggregates($rootNodeAggregateQueryBuilder))); } public function findNodeAggregatesByType( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): iterable { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->where('h.contentstreamid = :contentStreamId') + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery(); + $queryBuilder ->andWhere('n.nodetypename = :nodeTypeName') ->setParameters([ - 'contentStreamId' => $contentStreamId->value, + 'contentStreamId' => $this->contentStreamId->value, 'nodeTypeName' => $nodeTypeName->value, ]); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } public function findNodeAggregateById( - ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId ): ?NodeAggregate { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->tableNamePrefix . '_hierarchyrelation', 'h') - ->innerJoin('h', $this->tableNamePrefix . '_node', 'n', 'n.relationanchorpoint = h.childnodeanchor') - ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->where('n.nodeaggregateid = :nodeAggregateId') - ->andWhere('h.contentstreamid = :contentStreamId') + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() + ->andWhere('n.nodeaggregateid = :nodeAggregateId') + ->orderBy('n.relationanchorpoint', 'DESC') ->setParameters([ 'nodeAggregateId' => $nodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value + 'contentStreamId' => $this->contentStreamId->value ]); return $this->nodeFactory->mapNodeRowsToNodeAggregate( $this->fetchRows($queryBuilder), - $contentStreamId, + $this->contentStreamId, VisibilityConstraints::withoutRestrictions() ); } @@ -204,37 +179,38 @@ public function findNodeAggregateById( * @return iterable */ public function findParentNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId ): iterable { - $queryBuilder = $this->createQueryBuilder() - ->select('pn.*, ph.name, ph.contentstreamid, ph.subtreetags, pdsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->tableNamePrefix . '_node', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') - ->innerJoin('ph', $this->tableNamePrefix . '_dimensionspacepoints', 'pdsp', 'pdsp.hash = ph.dimensionspacepointhash') - ->where('cn.nodeaggregateid = :nodeAggregateId') - ->andWhere('ph.contentstreamid = :contentStreamId') + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->andWhere('ch.contentstreamid = :contentStreamId') + ->andWhere('cn.nodeaggregateid = :nodeAggregateId') ->setParameters([ 'nodeAggregateId' => $childNodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value + 'contentStreamId' => $this->contentStreamId->value ]); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } - public function findParentNodeAggregateByChildOriginDimensionSpacePoint( - ContentStreamId $contentStreamId, - NodeAggregateId $childNodeAggregateId, - OriginDimensionSpacePoint $childOriginDimensionSpacePoint - ): ?NodeAggregate { + /** + * @return iterable + */ + public function findChildNodeAggregates( + NodeAggregateId $parentNodeAggregateId + ): iterable { + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamId); + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); + } + + public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $childOriginDimensionSpacePoint): ?NodeAggregate + { $subQueryBuilder = $this->createQueryBuilder() ->select('pn.nodeaggregateid') - ->from($this->tableNamePrefix . '_node', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'pn') + ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('ch.dimensionspacepointhash = :childOriginDimensionSpacePointHash') ->andWhere('cn.nodeaggregateid = :childNodeAggregateId') @@ -242,75 +218,41 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( $queryBuilder = $this->createQueryBuilder() ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->nodeQueryBuilder->contentGraphTableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') ->andWhere('h.contentstreamid = :contentStreamId') ->setParameters([ - 'contentStreamId' => $contentStreamId->value, + 'contentStreamId' => $this->contentStreamId->value, 'childNodeAggregateId' => $childNodeAggregateId->value, 'childOriginDimensionSpacePointHash' => $childOriginDimensionSpacePoint->hash, ]); return $this->nodeFactory->mapNodeRowsToNodeAggregate( $this->fetchRows($queryBuilder), - $contentStreamId, + $this->contentStreamId, VisibilityConstraints::withoutRestrictions() ); } - /** - * @return iterable - */ - public function findChildNodeAggregates( - ContentStreamId $contentStreamId, - NodeAggregateId $parentNodeAggregateId - ): iterable { - $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $contentStreamId); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); - } - - /** - * @return iterable - */ - public function findChildNodeAggregatesByName( - ContentStreamId $contentStreamId, - NodeAggregateId $parentNodeAggregateId, - NodeName $name - ): iterable { - $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $contentStreamId) - ->andWhere('ch.name = :relationName') - ->setParameter('relationName', $name->value); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); - } - - /** - * @return iterable - */ - public function findTetheredChildNodeAggregates( - ContentStreamId $contentStreamId, - NodeAggregateId $parentNodeAggregateId - ): iterable { - $queryBuilder = $this->buildChildNodeAggregateQuery($parentNodeAggregateId, $contentStreamId) + public function findTetheredChildNodeAggregates(NodeAggregateId $parentNodeAggregateId): iterable + { + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamId) ->andWhere('cn.classification = :tetheredClassification') ->setParameter('tetheredClassification', NodeAggregateClassification::CLASSIFICATION_TETHERED->value); - return $this->mapQueryBuilderToNodeAggregates($queryBuilder, $contentStreamId); + + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); } - public function getDimensionSpacePointsOccupiedByChildNodeName( - ContentStreamId $contentStreamId, - NodeName $nodeName, - NodeAggregateId $parentNodeAggregateId, - OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, - DimensionSpacePointSet $dimensionSpacePointsToCheck - ): DimensionSpacePointSet { + public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck): DimensionSpacePointSet + { $queryBuilder = $this->createQueryBuilder() ->select('dsp.dimensionspacepoint, h.dimensionspacepointhash') - ->from($this->tableNamePrefix . '_hierarchyrelation', 'h') - ->innerJoin('h', $this->tableNamePrefix . '_node', 'n', 'n.relationanchorpoint = h.parentnodeanchor') - ->innerJoin('h', $this->tableNamePrefix . '_dimensionspacepoints', 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = n.relationanchorpoint') + ->from($this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h') + ->innerJoin('h', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'n', 'n.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('h', $this->nodeQueryBuilder->contentGraphTableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :parentNodeAggregateId') ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -320,7 +262,7 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ->setParameters([ 'parentNodeAggregateId' => $parentNodeAggregateId->value, 'parentNodeOriginDimensionSpacePointHash' => $parentNodeOriginDimensionSpacePoint->hash, - 'contentStreamId' => $contentStreamId->value, + 'contentStreamId' => $this->contentStreamId->value, 'dimensionSpacePointHashes' => $dimensionSpacePointsToCheck->getPointHashes(), 'nodeName' => $nodeName->value ], [ @@ -330,14 +272,29 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( foreach ($this->fetchRows($queryBuilder) as $hierarchyRelationData) { $dimensionSpacePoints[$hierarchyRelationData['dimensionspacepointhash']] = DimensionSpacePoint::fromJsonString($hierarchyRelationData['dimensionspacepoint']); } + return new DimensionSpacePointSet($dimensionSpacePoints); } + /** + * @return iterable + */ + public function findChildNodeAggregatesByName( + NodeAggregateId $parentNodeAggregateId, + NodeName $name + ): iterable { + $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamId) + ->andWhere('ch.name = :relationName') + ->setParameter('relationName', $name->value); + + return $this->mapQueryBuilderToNodeAggregates($queryBuilder); + } + public function countNodes(): int { $queryBuilder = $this->createQueryBuilder() ->select('COUNT(*)') - ->from($this->tableNamePrefix . '_node'); + ->from($this->nodeQueryBuilder->contentGraphTableNames->node()); $result = $queryBuilder->execute(); if (!$result instanceof Result) { throw new \RuntimeException(sprintf('Failed to count nodes. Expected result to be of type %s, got: %s', Result::class, get_debug_type($result)), 1701444550); @@ -351,10 +308,7 @@ public function countNodes(): int public function findUsedNodeTypeNames(): iterable { - $rows = $this->fetchRows($this->createQueryBuilder() - ->select('DISTINCT nodetypename') - ->from($this->tableNamePrefix . '_node')); - return array_map(static fn (array $row) => NodeTypeName::fromString($row['nodetypename']), $rows); + return array_map(static fn (array $row) => NodeTypeName::fromString($row['nodetypename']), $this->nodeQueryBuilder->findUsedNodeTypeNames()); } /** @@ -366,25 +320,6 @@ public function getSubgraphs(): array return $this->subgraphs; } - private function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId): QueryBuilder - { - return $this->createQueryBuilder() - ->select('cn.*, ch.name, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->tableNamePrefix . '_node', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->tableNamePrefix . '_dimensionspacepoints', 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') - ->innerJoin('ch', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') - ->where('pn.nodeaggregateid = :parentNodeAggregateId') - ->andWhere('ph.contentstreamid = :contentStreamId') - ->andWhere('ch.contentstreamid = :contentStreamId') - ->orderBy('ch.position') - ->setParameters([ - 'parentNodeAggregateId' => $parentNodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value, - ]); - } - private function createQueryBuilder(): QueryBuilder { return $this->client->getConnection()->createQueryBuilder(); @@ -394,11 +329,11 @@ private function createQueryBuilder(): QueryBuilder * @param QueryBuilder $queryBuilder * @return iterable */ - private function mapQueryBuilderToNodeAggregates(QueryBuilder $queryBuilder, ContentStreamId $contentStreamId): iterable + private function mapQueryBuilderToNodeAggregates(QueryBuilder $queryBuilder): iterable { return $this->nodeFactory->mapNodeRowsToNodeAggregates( $this->fetchRows($queryBuilder), - $contentStreamId, + $this->contentStreamId, VisibilityConstraints::withoutRestrictions() ); } @@ -418,4 +353,14 @@ private function fetchRows(QueryBuilder $queryBuilder): array throw new \RuntimeException(sprintf('Failed to fetch rows from database: %s', $e->getMessage()), 1701444358, $e); } } + + public function getWorkspaceName(): WorkspaceName + { + return $this->workspaceName; + } + + public function getContentStreamId(): ContentStreamId + { + return $this->contentStreamId; + } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index c1a278822d7..d51537631e3 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -14,12 +14,11 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository; -use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\Exception as DbalDriverException; use Doctrine\DBAL\Exception as DbalException; use Doctrine\DBAL\ForwardCompatibility\Result; -use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\QueryBuilder; +use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; @@ -42,28 +41,14 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSubtreeFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\ExpandedNodeTypeCriteria; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\NodeTypeCriteria; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Ordering\Ordering; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Ordering\OrderingDirection; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Ordering\TimestampField; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Pagination\Pagination; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\AndCriteria; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\NegateCriteria; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\OrCriteria; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueContains; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueCriteriaInterface; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueEndsWith; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueEquals; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueGreaterThan; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueGreaterThanOrEqual; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueLessThan; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueLessThanOrEqual; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\Criteria\PropertyValueStartsWith; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; use Neos\ContentRepository\Core\Projection\ContentGraph\References; -use Neos\ContentRepository\Core\Projection\ContentGraph\SearchTerm; use Neos\ContentRepository\Core\Projection\ContentGraph\Subtree; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; @@ -72,6 +57,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Node\PropertyName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * The content subgraph application repository @@ -101,18 +87,21 @@ */ final class ContentSubgraph implements ContentSubgraphInterface { - private int $dynamicParameterCount = 0; + private readonly NodeQueryBuilder $nodeQueryBuilder; public function __construct( private readonly ContentRepositoryId $contentRepositoryId, + /** @phpstan-ignore-next-line */ + private readonly WorkspaceName $workspaceName, private readonly ContentStreamId $contentStreamId, private readonly DimensionSpacePoint $dimensionSpacePoint, private readonly VisibilityConstraints $visibilityConstraints, private readonly DbalClientInterface $client, private readonly NodeFactory $nodeFactory, private readonly NodeTypeManager $nodeTypeManager, - private readonly string $tableNamePrefix + string $tableNamePrefix ) { + $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $tableNamePrefix); } public function getIdentity(): ContentSubgraphIdentity @@ -168,45 +157,23 @@ public function countBackReferences(NodeAggregateId $nodeAggregateId, CountBackR public function findNodeById(NodeAggregateId $nodeAggregateId): ?Node { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value) - ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeByIdQuery($nodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); $this->addSubtreeTagConstraints($queryBuilder); return $this->fetchNode($queryBuilder); } public function findRootNodeByType(NodeTypeName $nodeTypeName): ?Node { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('n.nodetypename = :nodeTypeName')->setParameter('nodeTypeName', $nodeTypeName->value) - ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('n.classification = :nodeAggregateClassification') - ->setParameter('nodeAggregateClassification', NodeAggregateClassification::CLASSIFICATION_ROOT->value); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamId, $this->dimensionSpacePoint) + ->andWhere('n.nodetypename = :nodeTypeName')->setParameter('nodeTypeName', $nodeTypeName->value) + ->andWhere('n.classification = :nodeAggregateClassification')->setParameter('nodeAggregateClassification', NodeAggregateClassification::CLASSIFICATION_ROOT->value); $this->addSubtreeTagConstraints($queryBuilder); return $this->fetchNode($queryBuilder); } public function findParentNode(NodeAggregateId $childNodeAggregateId): ?Node { - $queryBuilder = $this->createQueryBuilder() - ->select('pn.*, ch.name, ch.subtreetags') - ->from($this->tableNamePrefix . '_node', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') - ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) - ->andWhere('ph.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('ch.contentstreamid = :contentStreamId') - ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash'); + $queryBuilder = $this->nodeQueryBuilder->buildBasicParentNodeQuery($childNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); $this->addSubtreeTagConstraints($queryBuilder, 'ph'); return $this->fetchNode($queryBuilder); } @@ -238,14 +205,7 @@ public function findNodeByAbsolutePath(AbsoluteNodePath $path): ?Node */ private function findChildNodeConnectedThroughEdgeName(NodeAggregateId $parentNodeAggregateId, NodeName $nodeName): ?Node { - $queryBuilder = $this->createQueryBuilder() - ->select('cn.*, h.name, h.subtreetags') - ->from($this->tableNamePrefix . '_node', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = h.childnodeanchor') - ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) - ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) + $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint) ->andWhere('h.name = :edgeName')->setParameter('edgeName', $nodeName->value); $this->addSubtreeTagConstraints($queryBuilder); return $this->fetchNode($queryBuilder); @@ -291,8 +251,8 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi $queryBuilderInitial = $this->createQueryBuilder() // @see https://mariadb.com/kb/en/library/recursive-common-table-expressions-overview/#cast-to-avoid-data-truncation ->select('n.*, h.name, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); @@ -301,15 +261,15 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi $queryBuilderRecursive = $this->createQueryBuilder() ->select('c.*, h.name, h.subtreetags, p.nodeaggregateid AS parentNodeAggregateId, p.level + 1 AS level, h.position') ->from('tree', 'p') - ->innerJoin('p', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.parentnodeanchor = p.relationanchorpoint') - ->innerJoin('p', $this->tableNamePrefix . '_node', 'c', 'c.relationanchorpoint = h.childnodeanchor') + ->innerJoin('p', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') + ->innerJoin('p', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'c', 'c.relationanchorpoint = h.childnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); if ($filter->maximumLevels !== null) { $queryBuilderRecursive->andWhere('p.level < :maximumLevels')->setParameter('maximumLevels', $filter->maximumLevels); } if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilderRecursive, $filter->nodeTypes, 'c'); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderRecursive, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'c'); } $this->addSubtreeTagConstraints($queryBuilderRecursive); @@ -383,9 +343,9 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose { $queryBuilderInitial = $this->createQueryBuilder() ->select('n.*, ph.name, ph.subtreetags, ph.parentnodeanchor') - ->from($this->tableNamePrefix . '_node', 'n') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'n.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); @@ -394,21 +354,15 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose $queryBuilderRecursive = $this->createQueryBuilder() ->select('pn.*, h.name, h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') - ->innerJoin('cn', $this->tableNamePrefix . '_node', 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('cn', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->createQueryBuilder() - ->select('*') - ->from('ancestry', 'pn') - ->setMaxResults(1) - ->setParameter('contentStreamId', $this->contentStreamId->value) - ->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilderCte, $filter->nodeTypes, 'pn'); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'pn'); } $nodeRows = $this->fetchCteResults( $queryBuilderInitial, @@ -446,21 +400,18 @@ public function countDescendantNodes(NodeAggregateId $entryNodeAggregateId, Coun public function countNodes(): int { - $queryBuilder = $this->createQueryBuilder() - ->select('COUNT(*)') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamId, $this->dimensionSpacePoint, 'n', 'COUNT(*)'); try { $result = $this->executeQuery($queryBuilder)->fetchOne(); - if (!is_int($result)) { - throw new \RuntimeException(sprintf('Expected result to be of type integer but got: %s', get_debug_type($result)), 1678366902); - } - return $result; } catch (DbalDriverException | DbalException $e) { throw new \RuntimeException(sprintf('Failed to count all nodes: %s', $e->getMessage()), 1678364741, $e); } + + if (!is_int($result)) { + throw new \RuntimeException(sprintf('Expected result to be of type integer but got: %s', get_debug_type($result)), 1678366902); + } + + return $result; } public function jsonSerialize(): ContentSubgraphIdentity @@ -488,12 +439,6 @@ private function createQueryBuilder(): QueryBuilder return $this->client->getConnection()->createQueryBuilder(); } - private function createUniqueParameterName(): string - { - return 'param_' . (++$this->dynamicParameterCount); - } - - private function addSubtreeTagConstraints(QueryBuilder $queryBuilder, string $hierarchyRelationTableAlias = 'h'): void { $hierarchyRelationTablePrefix = $hierarchyRelationTableAlias === '' ? '' : $hierarchyRelationTableAlias . '.'; @@ -504,119 +449,17 @@ private function addSubtreeTagConstraints(QueryBuilder $queryBuilder, string $hi } } - private function addNodeTypeCriteria(QueryBuilder $queryBuilder, NodeTypeCriteria $nodeTypeCriteria, string $nodeTableAlias = 'n'): void - { - $nodeTablePrefix = $nodeTableAlias === '' ? '' : $nodeTableAlias . '.'; - $constraintsWithSubNodeTypes = ExpandedNodeTypeCriteria::create($nodeTypeCriteria, $this->nodeTypeManager); - $allowanceQueryPart = ''; - if (!$constraintsWithSubNodeTypes->explicitlyAllowedNodeTypeNames->isEmpty()) { - $allowanceQueryPart = $queryBuilder->expr()->in($nodeTablePrefix . 'nodetypename', ':explicitlyAllowedNodeTypeNames'); - $queryBuilder->setParameter('explicitlyAllowedNodeTypeNames', $constraintsWithSubNodeTypes->explicitlyAllowedNodeTypeNames->toStringArray(), Connection::PARAM_STR_ARRAY); - } - $denyQueryPart = ''; - if (!$constraintsWithSubNodeTypes->explicitlyDisallowedNodeTypeNames->isEmpty()) { - $denyQueryPart = $queryBuilder->expr()->notIn($nodeTablePrefix . 'nodetypename', ':explicitlyDisallowedNodeTypeNames'); - $queryBuilder->setParameter('explicitlyDisallowedNodeTypeNames', $constraintsWithSubNodeTypes->explicitlyDisallowedNodeTypeNames->toStringArray(), Connection::PARAM_STR_ARRAY); - } - if ($allowanceQueryPart && $denyQueryPart) { - if ($constraintsWithSubNodeTypes->isWildCardAllowed) { - $queryBuilder->andWhere($queryBuilder->expr()->or($allowanceQueryPart, $denyQueryPart)); - } else { - $queryBuilder->andWhere($queryBuilder->expr()->and($allowanceQueryPart, $denyQueryPart)); - } - } elseif ($allowanceQueryPart && !$constraintsWithSubNodeTypes->isWildCardAllowed) { - $queryBuilder->andWhere($allowanceQueryPart); - } elseif ($denyQueryPart) { - $queryBuilder->andWhere($denyQueryPart); - } - } - - private function addSearchTermConstraints(QueryBuilder $queryBuilder, SearchTerm $searchTerm, string $nodeTableAlias = 'n'): void - { - $queryBuilder->andWhere('JSON_SEARCH(' . $nodeTableAlias . '.properties, "one", :searchTermPattern, NULL, "$.*.value") IS NOT NULL')->setParameter('searchTermPattern', '%' . $searchTerm->term . '%'); - } - - private function addPropertyValueConstraints(QueryBuilder $queryBuilder, PropertyValueCriteriaInterface $propertyValue, string $nodeTableAlias = 'n'): void - { - $queryBuilder->andWhere($this->propertyValueConstraints($queryBuilder, $propertyValue, $nodeTableAlias)); - } - - private function propertyValueConstraints(QueryBuilder $queryBuilder, PropertyValueCriteriaInterface $propertyValue, string $nodeTableAlias): string - { - return match ($propertyValue::class) { - AndCriteria::class => (string)$queryBuilder->expr()->and($this->propertyValueConstraints($queryBuilder, $propertyValue->criteria1, $nodeTableAlias), $this->propertyValueConstraints($queryBuilder, $propertyValue->criteria2, $nodeTableAlias)), - NegateCriteria::class => 'NOT (' . $this->propertyValueConstraints($queryBuilder, $propertyValue->criteria, $nodeTableAlias) . ')', - OrCriteria::class => (string)$queryBuilder->expr()->or($this->propertyValueConstraints($queryBuilder, $propertyValue->criteria1, $nodeTableAlias), $this->propertyValueConstraints($queryBuilder, $propertyValue->criteria2, $nodeTableAlias)), - PropertyValueContains::class => $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, '%' . $propertyValue->value . '%', $nodeTableAlias, $propertyValue->caseSensitive), - PropertyValueEndsWith::class => $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, '%' . $propertyValue->value, $nodeTableAlias, $propertyValue->caseSensitive), - PropertyValueEquals::class => is_string($propertyValue->value) ? $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, $nodeTableAlias, $propertyValue->caseSensitive) : $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '=', $nodeTableAlias), - PropertyValueGreaterThan::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '>', $nodeTableAlias), - PropertyValueGreaterThanOrEqual::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '>=', $nodeTableAlias), - PropertyValueLessThan::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '<', $nodeTableAlias), - PropertyValueLessThanOrEqual::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '<=', $nodeTableAlias), - PropertyValueStartsWith::class => $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value . '%', $nodeTableAlias, $propertyValue->caseSensitive), - default => throw new \InvalidArgumentException(sprintf('Invalid/unsupported property value criteria "%s"', get_debug_type($propertyValue)), 1679561062), - }; - } - - private function comparePropertyValueStatement(QueryBuilder $queryBuilder, PropertyName $propertyName, string|int|float|bool $value, string $operator, string $nodeTableAlias): string - { - $paramName = $this->createUniqueParameterName(); - $paramType = match (gettype($value)) { - 'boolean' => ParameterType::BOOLEAN, - 'integer' => ParameterType::INTEGER, - default => ParameterType::STRING, - }; - $queryBuilder->setParameter($paramName, $value, $paramType); - return $this->extractPropertyValue($propertyName, $nodeTableAlias) . ' ' . $operator . ' :' . $paramName; - } - - private function extractPropertyValue(PropertyName $propertyName, string $nodeTableAlias): string - { - try { - $escapedPropertyName = addslashes(json_encode($propertyName->value, JSON_THROW_ON_ERROR)); - } catch (\JsonException $e) { - throw new \RuntimeException(sprintf('Failed to escape property name: %s', $e->getMessage()), 1679394579, $e); - } - return 'JSON_EXTRACT(' . $nodeTableAlias . '.properties, \'$.' . $escapedPropertyName . '.value\')'; - } - - private function searchPropertyValueStatement(QueryBuilder $queryBuilder, PropertyName $propertyName, string|bool|int|float $value, string $nodeTableAlias, bool $caseSensitive): string - { - try { - $escapedPropertyName = addslashes(json_encode($propertyName->value, JSON_THROW_ON_ERROR)); - } catch (\JsonException $e) { - throw new \RuntimeException(sprintf('Failed to escape property name: %s', $e->getMessage()), 1679394579, $e); - } - if (is_bool($value)) { - return 'JSON_SEARCH(' . $nodeTableAlias . '.properties, \'one\', \'' . ($value ? 'true' : 'false') . '\', NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; - } - $paramName = $this->createUniqueParameterName(); - $queryBuilder->setParameter($paramName, $value); - if ($caseSensitive) { - return 'JSON_SEARCH(' . $nodeTableAlias . '.properties COLLATE utf8mb4_bin, \'one\', :' . $paramName . ' COLLATE utf8mb4_bin, NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; - } - return 'JSON_SEARCH(' . $nodeTableAlias . '.properties, \'one\', :' . $paramName . ', NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; - } - private function buildChildNodesQuery(NodeAggregateId $parentNodeAggregateId, FindChildNodesFilter|CountChildNodesFilter $filter): QueryBuilder { - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->tableNamePrefix . '_node', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNamePrefix . '_node', 'n', 'h.childnodeanchor = n.relationanchorpoint') - ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) - ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash); + $queryBuilder = $this->nodeQueryBuilder->buildBasicChildNodesQuery($parentNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager)); } if ($filter->searchTerm !== null) { - $this->addSearchTermConstraints($queryBuilder, $filter->searchTerm); + $this->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->searchTerm); } if ($filter->propertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); + $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); } $this->addSubtreeTagConstraints($queryBuilder); return $queryBuilder; @@ -628,11 +471,11 @@ private function buildReferencesQuery(bool $backReferences, NodeAggregateId $nod $destinationTablePrefix = $backReferences ? 's' : 'd'; $queryBuilder = $this->createQueryBuilder() ->select("{$destinationTablePrefix}n.*, {$destinationTablePrefix}h.name, {$destinationTablePrefix}h.subtreetags, r.name AS referencename, r.properties AS referenceproperties") - ->from($this->tableNamePrefix . '_hierarchyrelation', 'sh') - ->innerJoin('sh', $this->tableNamePrefix . '_node', 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') - ->innerJoin('sh', $this->tableNamePrefix . '_referencerelation', 'r', 'r.nodeanchorpoint = sn.relationanchorpoint') - ->innerJoin('sh', $this->tableNamePrefix . '_node', 'dn', 'dn.nodeaggregateid = r.destinationnodeaggregateid') - ->innerJoin('sh', $this->tableNamePrefix . '_hierarchyrelation', 'dh', 'dh.childnodeanchor = dn.relationanchorpoint') + ->from($this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'sh') + ->innerJoin('sh', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') + ->innerJoin('sh', $this->nodeQueryBuilder->contentGraphTableNames->referenceRelation(), 'r', 'r.nodeanchorpoint = sn.relationanchorpoint') + ->innerJoin('sh', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'dn', 'dn.nodeaggregateid = r.destinationnodeaggregateid') + ->innerJoin('sh', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'dh', 'dh.childnodeanchor = dn.relationanchorpoint') ->where("{$sourceTablePrefix}n.nodeaggregateid = :nodeAggregateId")->setParameter('nodeAggregateId', $nodeAggregateId->value) ->andWhere('dh.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash') @@ -641,22 +484,19 @@ private function buildReferencesQuery(bool $backReferences, NodeAggregateId $nod $this->addSubtreeTagConstraints($queryBuilder, 'dh'); $this->addSubtreeTagConstraints($queryBuilder, 'sh'); if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes, "{$destinationTablePrefix}n"); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), "{$destinationTablePrefix}n"); } if ($filter->nodeSearchTerm !== null) { - $this->addSearchTermConstraints($queryBuilder, $filter->nodeSearchTerm, "{$destinationTablePrefix}n"); + $this->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->nodeSearchTerm, "{$destinationTablePrefix}n"); } if ($filter->nodePropertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilder, $filter->nodePropertyValue, "{$destinationTablePrefix}n"); + $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->nodePropertyValue, "{$destinationTablePrefix}n"); } if ($filter->referenceSearchTerm !== null) { - $this->addSearchTermConstraints($queryBuilder, $filter->referenceSearchTerm, 'r'); + $this->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->referenceSearchTerm, 'r'); } if ($filter->referencePropertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilder, $filter->referencePropertyValue, 'r'); - } - if ($filter->nodePropertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilder, $filter->nodePropertyValue, "{$destinationTablePrefix}n"); + $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->referencePropertyValue, 'r'); } if ($filter->referenceName !== null) { $queryBuilder->andWhere('r.name = :referenceName')->setParameter('referenceName', $filter->referenceName->value); @@ -678,42 +518,17 @@ private function buildReferencesQuery(bool $backReferences, NodeAggregateId $nod private function buildSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, FindPrecedingSiblingNodesFilter|FindSucceedingSiblingNodesFilter $filter): QueryBuilder { - $parentNodeAnchorSubQuery = $this->createQueryBuilder() - ->select('sh.parentnodeanchor') - ->from($this->tableNamePrefix . '_hierarchyrelation', 'sh') - ->innerJoin('sh', $this->tableNamePrefix . '_node', 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') - ->where('sn.nodeaggregateid = :siblingNodeAggregateId') - ->andWhere('sh.contentstreamid = :contentStreamId') - ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash'); - - $siblingPositionSubQuery = $this->createQueryBuilder() - ->select('sh.position') - ->from($this->tableNamePrefix . '_hierarchyrelation', 'sh') - ->innerJoin('sh', $this->tableNamePrefix . '_node', 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') - ->where('sn.nodeaggregateid = :siblingNodeAggregateId') - ->andWhere('sh.contentstreamid = :contentStreamId') - ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash'); - - $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') - ->from($this->tableNamePrefix . '_node', 'n') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $this->contentStreamId->value) - ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->andWhere('h.parentnodeanchor = (' . $parentNodeAnchorSubQuery->getSQL() . ')') - ->andWhere('n.nodeaggregateid != :siblingNodeAggregateId')->setParameter('siblingNodeAggregateId', $siblingNodeAggregateId->value) - ->andWhere('h.position ' . ($preceding ? '<' : '>') . ' (' . $siblingPositionSubQuery->getSQL() . ')') - ->orderBy('h.position', $preceding ? 'DESC' : 'ASC'); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeSiblingsQuery($preceding, $siblingNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); $this->addSubtreeTagConstraints($queryBuilder); if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilder, $filter->nodeTypes); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilder, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager)); } if ($filter->searchTerm !== null) { - $this->addSearchTermConstraints($queryBuilder, $filter->searchTerm); + $this->nodeQueryBuilder->addSearchTermConstraints($queryBuilder, $filter->searchTerm); } if ($filter->propertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); + $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilder, $filter->propertyValue); } if ($filter->pagination !== null) { $this->applyPagination($queryBuilder, $filter->pagination); @@ -728,11 +543,11 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId { $queryBuilderInitial = $this->createQueryBuilder() ->select('n.*, ph.name, ph.subtreetags, ph.parentnodeanchor') - ->from($this->tableNamePrefix . '_node', 'n') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') - ->innerJoin('ch', $this->tableNamePrefix . '_node', 'c', 'c.relationanchorpoint = ch.childnodeanchor') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'n.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -744,20 +559,15 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId $queryBuilderRecursive = $this->createQueryBuilder() ->select('pn.*, h.name, h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') - ->innerJoin('cn', $this->tableNamePrefix . '_node', 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('cn', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->createQueryBuilder() - ->select('*') - ->from('ancestry', 'pn') - ->setParameter('contentStreamId', $this->contentStreamId->value) - ->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilderCte, $filter->nodeTypes, 'pn'); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager), 'pn'); } return compact('queryBuilderInitial', 'queryBuilderRecursive', 'queryBuilderCte'); } @@ -770,11 +580,11 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate $queryBuilderInitial = $this->createQueryBuilder() // @see https://mariadb.com/kb/en/library/recursive-common-table-expressions-overview/#cast-to-avoid-data-truncation ->select('n.*, h.name, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') - ->from($this->tableNamePrefix . '_node', 'n') + ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('n', $this->tableNamePrefix . '_node', 'p', 'p.relationanchorpoint = h.parentnodeanchor') - ->innerJoin('n', $this->tableNamePrefix . '_hierarchyrelation', 'ph', 'ph.childnodeanchor = p.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'p', 'p.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -785,26 +595,21 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate $queryBuilderRecursive = $this->createQueryBuilder() ->select('cn.*, h.name, h.subtreetags, pn.nodeaggregateid AS parentNodeAggregateId, pn.level + 1 AS level, h.position') ->from('tree', 'pn') - ->innerJoin('pn', $this->tableNamePrefix . '_hierarchyrelation', 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNamePrefix . '_node', 'cn', 'cn.relationanchorpoint = h.childnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); - $queryBuilderCte = $this->createQueryBuilder() - ->select('*') - ->from('tree', 'n') - ->setParameter('contentStreamId', $this->contentStreamId->value) - ->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) - ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); + $queryBuilderCte = $this->nodeQueryBuilder->buildBasicNodesCteQuery($entryNodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint, 'tree', 'n'); if ($filter->nodeTypes !== null) { - $this->addNodeTypeCriteria($queryBuilderCte, $filter->nodeTypes); + $this->nodeQueryBuilder->addNodeTypeCriteria($queryBuilderCte, ExpandedNodeTypeCriteria::create($filter->nodeTypes, $this->nodeTypeManager)); } if ($filter->searchTerm !== null) { - $this->addSearchTermConstraints($queryBuilderCte, $filter->searchTerm); + $this->nodeQueryBuilder->addSearchTermConstraints($queryBuilderCte, $filter->searchTerm); } if ($filter->propertyValue !== null) { - $this->addPropertyValueConstraints($queryBuilderCte, $filter->propertyValue); + $this->nodeQueryBuilder->addPropertyValueConstraints($queryBuilderCte, $filter->propertyValue); } return compact('queryBuilderInitial', 'queryBuilderRecursive', 'queryBuilderCte'); } @@ -817,7 +622,7 @@ private function applyOrdering(QueryBuilder $queryBuilder, Ordering $ordering, s OrderingDirection::DESCENDING => 'DESC', }; if ($orderingField->field instanceof PropertyName) { - $queryBuilder->addOrderBy($this->extractPropertyValue($orderingField->field, $nodeTableAlias), $order); + $queryBuilder->addOrderBy($this->nodeQueryBuilder->extractPropertyValue($orderingField->field, $nodeTableAlias), $order); } else { $timestampColumnName = match ($orderingField->field) { TimestampField::CREATED => 'created', @@ -832,9 +637,7 @@ private function applyOrdering(QueryBuilder $queryBuilder, Ordering $ordering, s private function applyPagination(QueryBuilder $queryBuilder, Pagination $pagination): void { - $queryBuilder - ->setMaxResults($pagination->limit) - ->setFirstResult($pagination->offset); + $queryBuilder->setMaxResults($pagination->limit)->setFirstResult($pagination->offset); } /** diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php index 1a409b5abc6..374d750c3ad 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php @@ -15,6 +15,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository; use Doctrine\DBAL\Connection; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentRepository\Core\DimensionSpace\AbstractDimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; @@ -30,10 +31,13 @@ final class DimensionSpacePointsRepository */ private array $dimensionspacePointsRuntimeCache = []; + private readonly ContentGraphTableNames $contentGraphTableNames; + public function __construct( private readonly Connection $databaseConnection, - private readonly string $tableNamePrefix + string $tableNamePrefix ) { + $this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); } public function insertDimensionSpacePoint(AbstractDimensionSpacePoint $dimensionSpacePoint): void @@ -81,7 +85,7 @@ public function getOriginDimensionSpacePointByHash(string $hash): OriginDimensio private function writeDimensionSpacePoint(string $hash, string $dimensionSpacePointCoordinatesJson): void { $this->databaseConnection->executeStatement( - 'INSERT IGNORE INTO ' . $this->tableNamePrefix . '_dimensionspacepoints (hash, dimensionspacepoint) VALUES (:dimensionspacepointhash, :dimensionspacepoint)', + 'INSERT IGNORE INTO ' . $this->contentGraphTableNames->dimensionSpacePoints() . ' (hash, dimensionspacepoint) VALUES (:dimensionspacepointhash, :dimensionspacepoint)', [ 'dimensionspacepointhash' => $hash, 'dimensionspacepoint' => $dimensionSpacePointCoordinatesJson @@ -96,7 +100,7 @@ private function getCoordinatesByHashFromRuntimeCache(string $hash): ?string private function fillRuntimeCacheFromDatabase(): void { - $allDimensionSpacePoints = $this->databaseConnection->fetchAllAssociative('SELECT hash, dimensionspacepoint FROM ' . $this->tableNamePrefix . '_dimensionspacepoints'); + $allDimensionSpacePoints = $this->databaseConnection->fetchAllAssociative('SELECT hash, dimensionspacepoint FROM ' . $this->contentGraphTableNames->dimensionSpacePoints()); foreach ($allDimensionSpacePoints as $dimensionSpacePointRow) { $this->dimensionspacePointsRuntimeCache[(string)$dimensionSpacePointRow['hash']] = (string)$dimensionSpacePointRow['dimensionspacepoint']; } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php new file mode 100644 index 00000000000..c04bb1c72f3 --- /dev/null +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -0,0 +1,322 @@ +contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); + } + + public function buildBasicNodeAggregateQuery(): QueryBuilder + { + $queryBuilder = $this->createQueryBuilder() + ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->from($this->contentGraphTableNames->node(), 'n') + ->innerJoin('n', $this->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->contentGraphTableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->where('h.contentstreamid = :contentStreamId'); + + return $queryBuilder; + } + + public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId): QueryBuilder + { + return $this->createQueryBuilder() + ->select('cn.*, ch.name, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') + ->from($this->contentGraphTableNames->node(), 'pn') + ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->contentGraphTableNames->dimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') + ->innerJoin('ch', $this->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->where('pn.nodeaggregateid = :parentNodeAggregateId') + ->andWhere('ph.contentstreamid = :contentStreamId') + ->andWhere('ch.contentstreamid = :contentStreamId') + ->orderBy('ch.position') + ->setParameters([ + 'parentNodeAggregateId' => $parentNodeAggregateId->value, + 'contentStreamId' => $contentStreamId->value, + ]); + } + + public function buildFindRootNodeAggregatesQuery(ContentStreamId $contentStreamId, FindRootNodeAggregatesFilter $filter): QueryBuilder + { + $queryBuilder = $this->buildBasicNodeAggregateQuery() + ->andWhere('h.parentnodeanchor = :rootEdgeParentAnchorId') + ->setParameters([ + 'contentStreamId' => $contentStreamId->value, + 'rootEdgeParentAnchorId' => NodeRelationAnchorPoint::forRootEdge()->value, + ]); + + if ($filter->nodeTypeName !== null) { + $queryBuilder->andWhere('n.nodetypename = :nodeTypeName')->setParameter('nodeTypeName', $filter->nodeTypeName->value); + } + + return $queryBuilder; + } + + public function buildBasicNodeQuery(ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, string $nodeTableAlias = 'n', string $select = 'n.*, h.name, h.subtreetags'): QueryBuilder + { + return $this->createQueryBuilder() + ->select($select) + ->from($this->contentGraphTableNames->node(), $nodeTableAlias) + ->innerJoin($nodeTableAlias, $this->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') + ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) + ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); + } + + public function buildBasicNodeByIdQuery(NodeAggregateId $nodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + { + return $this->buildBasicNodeQuery($contentStreamId, $dimensionSpacePoint) + ->andWhere('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value); + } + + public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + { + return $this->createQueryBuilder() + ->select('n.*, h.name, h.subtreetags') + ->from($this->contentGraphTableNames->node(), 'pn') + ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->contentGraphTableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') + ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) + ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) + ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); + } + + public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + { + return $this->createQueryBuilder() + ->select('pn.*, ch.name, ch.subtreetags') + ->from($this->contentGraphTableNames->node(), 'pn') + ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') + ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) + ->andWhere('ph.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) + ->andWhere('ch.contentstreamid = :contentStreamId') + ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash) + ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash'); + } + + public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder + { + $sharedSubQuery = $this->createQueryBuilder() + ->from($this->contentGraphTableNames->hierachyRelation(), 'sh') + ->innerJoin('sh', $this->contentGraphTableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') + ->where('sn.nodeaggregateid = :siblingNodeAggregateId') + ->andWhere('sh.contentstreamid = :contentStreamId') + ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash'); + + $parentNodeAnchorSubQuery = (clone $sharedSubQuery)->select('sh.parentnodeanchor'); + $siblingPositionSubQuery = (clone $sharedSubQuery)->select('sh.position'); + + return $this->buildBasicNodeQuery($contentStreamId, $dimensionSpacePoint) + ->andWhere('h.parentnodeanchor = (' . $parentNodeAnchorSubQuery->getSQL() . ')') + ->andWhere('n.nodeaggregateid != :siblingNodeAggregateId')->setParameter('siblingNodeAggregateId', $siblingNodeAggregateId->value) + ->andWhere('h.position ' . ($preceding ? '<' : '>') . ' (' . $siblingPositionSubQuery->getSQL() . ')') + ->orderBy('h.position', $preceding ? 'DESC' : 'ASC'); + } + + public function buildBasicNodesCteQuery(NodeAggregateId $entryNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, string $cteName = 'ancestry', string $cteAlias = 'pn'): QueryBuilder + { + return $this->createQueryBuilder() + ->select('*') + ->from($cteName, $cteAlias) + ->setParameter('contentStreamId', $contentStreamId->value) + ->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash) + ->setParameter('entryNodeAggregateId', $entryNodeAggregateId->value); + } + + public function addNodeTypeCriteria(QueryBuilder $queryBuilder, ExpandedNodeTypeCriteria $constraintsWithSubNodeTypes, string $nodeTableAlias = 'n'): void + { + $nodeTablePrefix = $nodeTableAlias === '' ? '' : $nodeTableAlias . '.'; + $allowanceQueryPart = ''; + if (!$constraintsWithSubNodeTypes->explicitlyAllowedNodeTypeNames->isEmpty()) { + $allowanceQueryPart = $queryBuilder->expr()->in($nodeTablePrefix . 'nodetypename', ':explicitlyAllowedNodeTypeNames'); + $queryBuilder->setParameter('explicitlyAllowedNodeTypeNames', $constraintsWithSubNodeTypes->explicitlyAllowedNodeTypeNames->toStringArray(), Connection::PARAM_STR_ARRAY); + } + $denyQueryPart = ''; + if (!$constraintsWithSubNodeTypes->explicitlyDisallowedNodeTypeNames->isEmpty()) { + $denyQueryPart = $queryBuilder->expr()->notIn($nodeTablePrefix . 'nodetypename', ':explicitlyDisallowedNodeTypeNames'); + $queryBuilder->setParameter('explicitlyDisallowedNodeTypeNames', $constraintsWithSubNodeTypes->explicitlyDisallowedNodeTypeNames->toStringArray(), Connection::PARAM_STR_ARRAY); + } + if ($allowanceQueryPart && $denyQueryPart) { + if ($constraintsWithSubNodeTypes->isWildCardAllowed) { + $queryBuilder->andWhere($queryBuilder->expr()->or($allowanceQueryPart, $denyQueryPart)); + } else { + $queryBuilder->andWhere($queryBuilder->expr()->and($allowanceQueryPart, $denyQueryPart)); + } + } elseif ($allowanceQueryPart && !$constraintsWithSubNodeTypes->isWildCardAllowed) { + $queryBuilder->andWhere($allowanceQueryPart); + } elseif ($denyQueryPart) { + $queryBuilder->andWhere($denyQueryPart); + } + } + + public function addSearchTermConstraints(QueryBuilder $queryBuilder, SearchTerm $searchTerm, string $nodeTableAlias = 'n'): void + { + $queryBuilder->andWhere('JSON_SEARCH(' . $nodeTableAlias . '.properties, "one", :searchTermPattern, NULL, "$.*.value") IS NOT NULL')->setParameter('searchTermPattern', '%' . $searchTerm->term . '%'); + } + + public function addPropertyValueConstraints(QueryBuilder $queryBuilder, PropertyValueCriteriaInterface $propertyValue, string $nodeTableAlias = 'n'): void + { + $queryBuilder->andWhere($this->propertyValueConstraints($queryBuilder, $propertyValue, $nodeTableAlias)); + } + + private function propertyValueConstraints(QueryBuilder $queryBuilder, PropertyValueCriteriaInterface $propertyValue, string $nodeTableAlias): string + { + return match ($propertyValue::class) { + AndCriteria::class => (string)$queryBuilder->expr()->and($this->propertyValueConstraints($queryBuilder, $propertyValue->criteria1, $nodeTableAlias), $this->propertyValueConstraints($queryBuilder, $propertyValue->criteria2, $nodeTableAlias)), + NegateCriteria::class => 'NOT (' . $this->propertyValueConstraints($queryBuilder, $propertyValue->criteria, $nodeTableAlias) . ')', + OrCriteria::class => (string)$queryBuilder->expr()->or($this->propertyValueConstraints($queryBuilder, $propertyValue->criteria1, $nodeTableAlias), $this->propertyValueConstraints($queryBuilder, $propertyValue->criteria2, $nodeTableAlias)), + PropertyValueContains::class => $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, '%' . $propertyValue->value . '%', $nodeTableAlias, $propertyValue->caseSensitive), + PropertyValueEndsWith::class => $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, '%' . $propertyValue->value, $nodeTableAlias, $propertyValue->caseSensitive), + PropertyValueEquals::class => is_string($propertyValue->value) ? $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, $nodeTableAlias, $propertyValue->caseSensitive) : $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '=', $nodeTableAlias), + PropertyValueGreaterThan::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '>', $nodeTableAlias), + PropertyValueGreaterThanOrEqual::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '>=', $nodeTableAlias), + PropertyValueLessThan::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '<', $nodeTableAlias), + PropertyValueLessThanOrEqual::class => $this->comparePropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value, '<=', $nodeTableAlias), + PropertyValueStartsWith::class => $this->searchPropertyValueStatement($queryBuilder, $propertyValue->propertyName, $propertyValue->value . '%', $nodeTableAlias, $propertyValue->caseSensitive), + default => throw new \InvalidArgumentException(sprintf('Invalid/unsupported property value criteria "%s"', get_debug_type($propertyValue)), 1679561062), + }; + } + + private function comparePropertyValueStatement(QueryBuilder $queryBuilder, PropertyName $propertyName, string|int|float|bool $value, string $operator, string $nodeTableAlias): string + { + $paramName = $this->createUniqueParameterName(); + $paramType = match (gettype($value)) { + 'boolean' => ParameterType::BOOLEAN, + 'integer' => ParameterType::INTEGER, + default => ParameterType::STRING, + }; + $queryBuilder->setParameter($paramName, $value, $paramType); + + return $this->extractPropertyValue($propertyName, $nodeTableAlias) . ' ' . $operator . ' :' . $paramName; + } + + public function extractPropertyValue(PropertyName $propertyName, string $nodeTableAlias): string + { + try { + $escapedPropertyName = addslashes(json_encode($propertyName->value, JSON_THROW_ON_ERROR)); + } catch (\JsonException $e) { + throw new \RuntimeException(sprintf('Failed to escape property name: %s', $e->getMessage()), 1679394579, $e); + } + + return 'JSON_EXTRACT(' . $nodeTableAlias . '.properties, \'$.' . $escapedPropertyName . '.value\')'; + } + + private function searchPropertyValueStatement(QueryBuilder $queryBuilder, PropertyName $propertyName, string|bool|int|float $value, string $nodeTableAlias, bool $caseSensitive): string + { + try { + $escapedPropertyName = addslashes(json_encode($propertyName->value, JSON_THROW_ON_ERROR)); + } catch (\JsonException $e) { + throw new \RuntimeException(sprintf('Failed to escape property name: %s', $e->getMessage()), 1679394579, $e); + } + if (is_bool($value)) { + return 'JSON_SEARCH(' . $nodeTableAlias . '.properties, \'one\', \'' . ($value ? 'true' : 'false') . '\', NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; + } + $paramName = $this->createUniqueParameterName(); + $queryBuilder->setParameter($paramName, $value); + if ($caseSensitive) { + return 'JSON_SEARCH(' . $nodeTableAlias . '.properties COLLATE utf8mb4_bin, \'one\', :' . $paramName . ' COLLATE utf8mb4_bin, NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; + } + + return 'JSON_SEARCH(' . $nodeTableAlias . '.properties, \'one\', :' . $paramName . ', NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; + } + + public function getTablenameForNode(): string + { + return $this->tableNamePrefix . '_node'; + } + + public function getTablenameForHierachyRelation(): string + { + return $this->tableNamePrefix . '_hierarchyrelation'; + } + + public function getTablenameForDimensionSpacePoints(): string + { + return $this->tableNamePrefix . '_dimensionspacepoints'; + } + + public function getTablenameForReferenceRelation(): string + { + return $this->tableNamePrefix . '_referencerelation'; + } + + /** + * @return array> + * @throws DBALException + */ + public function findUsedNodeTypeNames(): array + { + $rows = $this->fetchRows($this->createQueryBuilder() + ->select('DISTINCT nodetypename') + ->from($this->contentGraphTableNames->node())); + + return $rows; + } + + /** + * @return array> + * @throws DBALException + */ + public function fetchRows(QueryBuilder $queryBuilder): array + { + $result = $queryBuilder->execute(); + if (!$result instanceof Result) { + throw new \RuntimeException(sprintf('Failed to execute query. Expected result to be of type %s, got: %s', Result::class, get_debug_type($result)), 1701443535); + } + try { + return $result->fetchAllAssociative(); + } catch (DriverException $e) { + throw new \RuntimeException(sprintf('Failed to fetch rows from database: %s', $e->getMessage()), 1701444358, $e); + } + } + + private function createQueryBuilder(): QueryBuilder + { + return $this->connection->createQueryBuilder(); + } + + private function createUniqueParameterName(): string + { + return 'param_' . str_replace('-', '', UuidFactory::create()); + } +} diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php new file mode 100644 index 00000000000..ebb74ab5035 --- /dev/null +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/ContentHyperGraphFactory.php @@ -0,0 +1,62 @@ +contentRepositoryId->value, + 'Workspace' + )); + + $row = $this->databaseClient->getConnection()->executeQuery( + ' + SELECT * FROM ' . $tableName . ' + WHERE workspaceName = :workspaceName + ', + [ + 'workspaceName' => $workspaceName->value, + ] + )->fetchAssociative(); + + if ($row === false) { + throw new ContentStreamDoesNotExistYet('The workspace "' . $workspaceName->value . '" does not exist.', 1714839710); + } + + return $this->buildForWorkspaceAndContentStream($workspaceName, ContentStreamId::fromString($row['currentcontentstreamid'])); + } + + public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface + { + return new ContentHyperGraph($this->databaseClient, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNamePrefix, $workspaceName, $contentStreamId); + } +} diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php index 114df60ed58..c8a544e935a 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php @@ -26,9 +26,8 @@ use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\Feature\NodeVariation; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\Feature\SubtreeTagging; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\SchemaBuilder\HypergraphSchemaBuilder; -use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\ContentHypergraph; -use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\PostgreSQLAdapter\Infrastructure\PostgresDbalClientInterface; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Feature\ContentStreamForking\Event\ContentStreamWasForked; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; @@ -45,18 +44,16 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasUntagged; use Neos\ContentRepository\Core\Infrastructure\DbalCheckpointStorage; use Neos\ContentRepository\Core\Infrastructure\DbalSchemaDiff; -use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\CheckpointStorageStatusType; use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionStatus; -use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\EventStore\Model\Event\SequenceNumber; use Neos\EventStore\Model\EventEnvelope; /** * The alternate reality-aware hypergraph projector for the PostgreSQL backend via Doctrine DBAL * - * @implements ProjectionInterface + * @implements ProjectionInterface * @internal the parent Content Graph is public */ final class HypergraphProjection implements ProjectionInterface @@ -71,20 +68,13 @@ final class HypergraphProjection implements ProjectionInterface use NodeTypeChange; use NodeVariation; - /** - * @var ContentHypergraph|null Cache for the content graph returned by {@see getState()}, - * so that always the same instance is returned - */ - private ?ContentHypergraph $contentHypergraph = null; private DbalCheckpointStorage $checkpointStorage; private ProjectionHypergraph $projectionHypergraph; public function __construct( private readonly PostgresDbalClientInterface $databaseClient, - private readonly NodeFactory $nodeFactory, - private readonly ContentRepositoryId $contentRepositoryId, - private readonly NodeTypeManager $nodeTypeManager, private readonly string $tableNamePrefix, + private readonly ContentGraphFinder $contentGraphFinder ) { $this->projectionHypergraph = new ProjectionHypergraph($this->databaseClient, $this->tableNamePrefix); $this->checkpointStorage = new DbalCheckpointStorage( @@ -242,18 +232,9 @@ public function getCheckpointStorage(): DbalCheckpointStorage return $this->checkpointStorage; } - public function getState(): ContentHypergraph + public function getState(): ContentGraphFinder { - if (!$this->contentHypergraph) { - $this->contentHypergraph = new ContentHypergraph( - $this->databaseClient, - $this->nodeFactory, - $this->contentRepositoryId, - $this->nodeTypeManager, - $this->tableNamePrefix - ); - } - return $this->contentHypergraph; + return $this->contentGraphFinder; } protected function getProjectionHypergraph(): ProjectionHypergraph diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php index 49f9b7af5be..1ff5fbb1dbc 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php @@ -35,6 +35,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * The PostgreSQL adapter content hypergraph @@ -59,22 +60,23 @@ public function __construct( NodeFactory $nodeFactory, private readonly ContentRepositoryId $contentRepositoryId, private readonly NodeTypeManager $nodeTypeManager, - private readonly string $tableNamePrefix + private readonly string $tableNamePrefix, + public readonly WorkspaceName $workspaceName, + public readonly ContentStreamId $contentStreamId ) { $this->databaseClient = $databaseClient; $this->nodeFactory = $nodeFactory; } public function getSubgraph( - ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints ): ContentSubgraphInterface { - $index = $contentStreamId->value . '-' . $dimensionSpacePoint->hash . '-' . $visibilityConstraints->getHash(); + $index = $this->contentStreamId->value . '-' . $dimensionSpacePoint->hash . '-' . $visibilityConstraints->getHash(); if (!isset($this->subhypergraphs[$index])) { $this->subhypergraphs[$index] = new ContentSubhypergraph( $this->contentRepositoryId, - $contentStreamId, + $this->contentStreamId, $dimensionSpacePoint, $visibilityConstraints, $this->databaseClient, @@ -88,11 +90,9 @@ public function getSubgraph( } public function findRootNodeAggregateByType( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): NodeAggregate { $rootNodeAggregates = $this->findRootNodeAggregates( - $contentStreamId, FindRootNodeAggregatesFilter::create(nodeTypeName: $nodeTypeName) ); @@ -118,7 +118,6 @@ public function findRootNodeAggregateByType( } public function findRootNodeAggregates( - ContentStreamId $contentStreamId, FindRootNodeAggregatesFilter $filter, ): NodeAggregates { throw new \BadMethodCallException('method findRootNodeAggregates is not implemented yet.', 1645782874); @@ -128,17 +127,15 @@ public function findRootNodeAggregates( * @return \Iterator */ public function findNodeAggregatesByType( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): \Iterator { return new \Generator(); } public function findNodeAggregateById( - ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId ): ?NodeAggregate { - $query = HypergraphQuery::create($contentStreamId, $this->tableNamePrefix, true); + $query = HypergraphQuery::create($this->contentStreamId, $this->tableNamePrefix, true); $query = $query->withNodeAggregateId($nodeAggregateId); $nodeRows = $query->execute($this->getDatabaseConnection())->fetchAllAssociative(); @@ -150,7 +147,6 @@ public function findNodeAggregateById( } public function findParentNodeAggregateByChildOriginDimensionSpacePoint( - ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $childOriginDimensionSpacePoint ): ?NodeAggregate { @@ -172,7 +168,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( AND ch.contentstreamid = :contentStreamId )'; $parameters = [ - 'contentStreamId' => $contentStreamId->value, + 'contentStreamId' => $this->contentStreamId->value, 'childNodeAggregateId' => $childNodeAggregateId->value, 'childOriginDimensionSpacePointHash' => $childOriginDimensionSpacePoint->hash ]; @@ -192,10 +188,9 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( * @return iterable */ public function findParentNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId ): iterable { - $query = HypergraphParentQuery::create($contentStreamId, $this->tableNamePrefix); + $query = HypergraphParentQuery::create($this->contentStreamId, $this->tableNamePrefix); $query = $query->withChildNodeAggregateId($childNodeAggregateId); $nodeRows = $query->execute($this->getDatabaseConnection())->fetchAllAssociative(); @@ -210,11 +205,10 @@ public function findParentNodeAggregates( * @return iterable */ public function findChildNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId ): iterable { $query = HypergraphChildQuery::create( - $contentStreamId, + $this->contentStreamId, $parentNodeAggregateId, $this->tableNamePrefix ); @@ -231,12 +225,11 @@ public function findChildNodeAggregates( * @return iterable */ public function findChildNodeAggregatesByName( - ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId, NodeName $name ): iterable { $query = HypergraphChildQuery::create( - $contentStreamId, + $this->contentStreamId, $parentNodeAggregateId, $this->tableNamePrefix ); @@ -254,11 +247,10 @@ public function findChildNodeAggregatesByName( * @return iterable */ public function findTetheredChildNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId ): iterable { $query = HypergraphChildQuery::create( - $contentStreamId, + $this->contentStreamId, $parentNodeAggregateId, $this->tableNamePrefix ); @@ -270,14 +262,13 @@ public function findTetheredChildNodeAggregates( } public function getDimensionSpacePointsOccupiedByChildNodeName( - ContentStreamId $contentStreamId, NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, DimensionSpacePointSet $dimensionSpacePointsToCheck ): DimensionSpacePointSet { $query = HypergraphChildQuery::create( - $contentStreamId, + $this->contentStreamId, $parentNodeAggregateId, $this->tableNamePrefix, ['ch.dimensionspacepoint, ch.dimensionspacepointhash'] @@ -318,4 +309,14 @@ private function getDatabaseConnection(): DatabaseConnection { return $this->databaseClient->getConnection(); } + + public function getWorkspaceName(): WorkspaceName + { + return $this->workspaceName; + } + + public function getContentStreamId(): ContentStreamId + { + return $this->contentStreamId; + } } diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php index 71c076400b6..3f8189c6d01 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/HypergraphProjectionFactory.php @@ -7,6 +7,7 @@ use Neos\ContentGraph\PostgreSQLAdapter\Domain\Projection\HypergraphProjection; use Neos\ContentGraph\PostgreSQLAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\PostgreSQLAdapter\Infrastructure\PostgresDbalClientInterface; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\Factory\ProjectionFactoryDependencies; use Neos\ContentRepository\Core\Projection\ProjectionFactoryInterface; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; @@ -36,16 +37,16 @@ public function build( $projectionFactoryDependencies->contentRepositoryId ); - return new HypergraphProjection( - $this->dbalClient, - new NodeFactory( - $projectionFactoryDependencies->contentRepositoryId, - $projectionFactoryDependencies->nodeTypeManager, - $projectionFactoryDependencies->propertyConverter - ), + $nodeFactory = new NodeFactory( $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, - $tableNamePrefix + $projectionFactoryDependencies->propertyConverter + ); + + return new HypergraphProjection( + $this->dbalClient, + $tableNamePrefix, + new ContentGraphFinder(new ContentHyperGraphFactory($this->dbalClient, $nodeFactory, $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, $tableNamePrefix)) ); } } diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature index c374e45a337..af08ffefc48 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature @@ -77,7 +77,7 @@ Feature: Constraint checks on SetNodeReferences | sourceNodeAggregateId | "source-nodandaise" | | referenceName | "referenceProperty" | | references | [{"target":"anthony-destinode"}] | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" with code 1710407870 + Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" with code 1714839710 # checks for sourceNodeAggregateId Scenario: Try to reference nodes in a non-existent node aggregate diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php b/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php new file mode 100644 index 00000000000..bfb68749484 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php @@ -0,0 +1,31 @@ + + */ + private array $contenGraphInstances = []; + + public function __construct( + private readonly ContentGraphFactoryInterface $contentGraphFactory + ) { + } + + /** + * @throws WorkspaceDoesNotExist if there is no workspace with the provided name + * @throws ContentStreamDoesNotExistYet if the provided workspace does not resolve to an existing content stream + */ + public function fromWorkspaceName(WorkspaceName $workspaceName): ContentGraphInterface + { + if (isset($this->contenGraphInstances[$workspaceName->value])) { + return $this->contenGraphInstances[$workspaceName->value]; + } + + $this->contenGraphInstances[$workspaceName->value] = $this->contentGraphFactory->buildForWorkspace($workspaceName); + return $this->contenGraphInstances[$workspaceName->value]; + } + + /** + * @return ContentGraphInterface[] + */ + public function getInstances(): array + { + return $this->contenGraphInstances; + } + + public function reset(): void + { + $this->contenGraphInstances = []; + } + + /** + * @param WorkspaceName $workspaceName + * @param ContentStreamId $contentStreamId + * @return ContentGraphInterface + * @internal + */ + public function fromWorkspaceNameAndContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface + { + if (isset($this->contenGraphInstances[$workspaceName->value]) && $this->contenGraphInstances[$workspaceName->value]->getContentStreamId() === $contentStreamId) { + return $this->contenGraphInstances[$workspaceName->value]; + } + + return $this->contentGraphFactory->buildForWorkspaceAndContentStream($workspaceName, $contentStreamId); + } + + /** + * Stateful (dirty) override of the chosen ContentStreamId for a given workspace, it applies within the given closure. + * Implementations must ensure that requesting the contentStreamId for this workspace will resolve to the given + * override ContentStreamId and vice versa resolving the WorkspaceName from this ContentStreamId should result in the + * given WorkspaceName within the closure. + * + * @internal + */ + public function overrideContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId, \Closure $fn): void + { + $contentGraph = $this->contentGraphFactory->buildForWorkspaceAndContentStream($workspaceName, $contentStreamId); + $replacedAdapter = $this->contenGraphInstances[$workspaceName->value] ?? null; + $this->contenGraphInstances[$workspaceName->value] = $contentGraph; + + try { + $fn(); + } finally { + unset($this->contenGraphInstances[$workspaceName->value]); + if ($replacedAdapter) { + $this->contenGraphInstances[$workspaceName->value] = $replacedAdapter; + } + } + } +} diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFinderInterface.php b/Neos.ContentRepository.Core/Classes/ContentGraphFinderInterface.php new file mode 100644 index 00000000000..d934734d53e --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/ContentGraphFinderInterface.php @@ -0,0 +1,36 @@ +nodeTypeManager; } - public function getContentGraph(): ContentGraphInterface + public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface { - return $this->projectionState(ContentGraphInterface::class); + return $this->projectionState(ContentGraphFinder::class)->fromWorkspaceName($workspaceName); } public function getWorkspaceFinder(): WorkspaceFinder diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php index b23f23851c0..3ad56ed1a49 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php @@ -62,6 +62,7 @@ public function __construct( $contentDimensionSource, $contentDimensionZookeeper ); + $propertyConverter = new PropertyConverter($propertySerializer); $this->projectionFactoryDependencies = new ProjectionFactoryDependencies( $contentRepositoryId, $eventStore, @@ -100,7 +101,7 @@ public function getOrBuild(): ContentRepository $this->projectionFactoryDependencies->interDimensionalVariationGraph, $this->projectionFactoryDependencies->contentDimensionSource, $this->userIdProvider, - $this->clock, + $this->clock ); } return $this->contentRepository; @@ -120,11 +121,12 @@ public function getOrBuild(): ContentRepository public function buildService( ContentRepositoryServiceFactoryInterface $serviceFactory ): ContentRepositoryServiceInterface { + $serviceFactoryDependencies = ContentRepositoryServiceFactoryDependencies::create( $this->projectionFactoryDependencies, $this->getOrBuild(), $this->buildEventPersister(), - $this->projectionsAndCatchUpHooks->projections, + $this->projectionsAndCatchUpHooks->projections ); return $serviceFactory->build($serviceFactoryDependencies); } @@ -144,16 +146,16 @@ private function buildCommandBus(): CommandBus $this->projectionFactoryDependencies->nodeTypeManager, $this->projectionFactoryDependencies->contentDimensionZookeeper, $this->projectionFactoryDependencies->interDimensionalVariationGraph, - $this->projectionFactoryDependencies->propertyConverter + $this->projectionFactoryDependencies->propertyConverter, ), new DimensionSpaceCommandHandler( $this->projectionFactoryDependencies->contentDimensionZookeeper, - $this->projectionFactoryDependencies->interDimensionalVariationGraph + $this->projectionFactoryDependencies->interDimensionalVariationGraph, ), new NodeDuplicationCommandHandler( $this->projectionFactoryDependencies->nodeTypeManager, $this->projectionFactoryDependencies->contentDimensionZookeeper, - $this->projectionFactoryDependencies->interDimensionalVariationGraph + $this->projectionFactoryDependencies->interDimensionalVariationGraph, ) ); } diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php index ca9df198cee..9386f9bdb1d 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php @@ -46,7 +46,7 @@ private function __construct( public ContentRepository $contentRepository, // we don't need CommandBus, because this is included in ContentRepository->handle() public EventPersister $eventPersister, - public Projections $projections, + public Projections $projections ) { } @@ -57,7 +57,7 @@ public static function create( ProjectionFactoryDependencies $projectionFactoryDependencies, ContentRepository $contentRepository, EventPersister $eventPersister, - Projections $projections, + Projections $projections ): self { return new self( $projectionFactoryDependencies->contentRepositoryId, @@ -70,7 +70,7 @@ public static function create( $projectionFactoryDependencies->propertyConverter, $contentRepository, $eventPersister, - $projections, + $projections ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index f83f1cf83ae..a012a8357a6 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -27,6 +27,7 @@ use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindPrecedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; @@ -82,14 +83,16 @@ protected function requireContentStream( WorkspaceName $workspaceName, ContentRepository $contentRepository ): ContentStreamId { - $contentStreamId = ContentStreamIdOverride::resolveContentStreamIdForWorkspace($contentRepository, $workspaceName); - if (!$contentRepository->getContentStreamFinder()->hasContentStream($contentStreamId)) { + $contentStreamId = $contentRepository->getContentGraph($workspaceName)->getContentStreamId(); + $state = $contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId); + if ($state === null) { throw new ContentStreamDoesNotExistYet( - 'Content stream "' . $contentStreamId->value . '" does not exist yet.', + 'Content stream for "' . $workspaceName->value . '" does not exist yet.', 1521386692 ); } - if ($contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId) === ContentStreamState::STATE_CLOSED) { + + if ($state === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', 1710260081 @@ -156,19 +159,16 @@ protected function requireNodeTypeToNotBeOfTypeRoot(NodeType $nodeType): void } protected function requireRootNodeTypeToBeUnoccupied( - NodeTypeName $nodeTypeName, - ContentStreamId $contentStreamId, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph, + NodeTypeName $nodeTypeName ): void { try { - $rootNodeAggregate = $contentRepository->getContentGraph()->findRootNodeAggregateByType( - $contentStreamId, - $nodeTypeName - ); - throw RootNodeAggregateTypeIsAlreadyOccupied::butWasExpectedNotTo($nodeTypeName); - } catch (RootNodeAggregateDoesNotExist $exception) { - // all is well + $contentGraph->findRootNodeAggregateByType($nodeTypeName); + } catch (RootNodeAggregateDoesNotExist $_) { + return; } + + throw RootNodeAggregateTypeIsAlreadyOccupied::butWasExpectedNotTo($nodeTypeName); } /** @@ -259,24 +259,22 @@ protected function requireNodeTypeToAllowNumberOfReferencesInReference(Serialize /** * NodeType and NodeName must belong together to the same node, which is the to-be-checked one. * - * @param ContentStreamId $contentStreamId + * @param ContentGraphInterface $contentGraph * @param NodeType $nodeType * @param NodeName|null $nodeName * @param array|NodeAggregateId[] $parentNodeAggregateIds * @throws NodeConstraintException */ protected function requireConstraintsImposedByAncestorsAreMet( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeType $nodeType, ?NodeName $nodeName, - array $parentNodeAggregateIds, - ContentRepository $contentRepository + array $parentNodeAggregateIds ): void { foreach ($parentNodeAggregateIds as $parentNodeAggregateId) { $parentAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $parentNodeAggregateId, - $contentRepository + $contentGraph, + $parentNodeAggregateId ); if (!$parentAggregate->classification->isTethered()) { try { @@ -289,8 +287,7 @@ protected function requireConstraintsImposedByAncestorsAreMet( } foreach ( - $contentRepository->getContentGraph()->findParentNodeAggregates( - $contentStreamId, + $contentGraph->findParentNodeAggregates( $parentNodeAggregateId ) as $grandParentNodeAggregate ) { @@ -389,14 +386,9 @@ protected function areNodeTypeConstraintsImposedByGrandparentValid( ?NodeName $parentNodeName, NodeType $nodeType ): bool { - if ( - $parentNodeName + return !($parentNodeName && $grandParentsNodeType->hasTetheredNode($parentNodeName) - && !$this->getNodeTypeManager()->isNodeTypeAllowedAsChildToTetheredNode($grandParentsNodeType, $parentNodeName, $nodeType) - ) { - return false; - } - return true; + && !$this->getNodeTypeManager()->isNodeTypeAllowedAsChildToTetheredNode($grandParentsNodeType, $parentNodeName, $nodeType)); } /** @@ -404,12 +396,10 @@ protected function areNodeTypeConstraintsImposedByGrandparentValid( * @throws NodeAggregateCurrentlyDoesNotExist */ protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph, + NodeAggregateId $nodeAggregateId ): NodeAggregate { - $nodeAggregate = $contentRepository->getContentGraph()->findNodeAggregateById( - $contentStreamId, + $nodeAggregate = $contentGraph->findNodeAggregateById( $nodeAggregateId ); @@ -428,12 +418,10 @@ protected function requireProjectedNodeAggregate( * @throws NodeAggregateCurrentlyExists */ protected function requireProjectedNodeAggregateToNotExist( - ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph, + NodeAggregateId $nodeAggregateId ): void { - $nodeAggregate = $contentRepository->getContentGraph()->findNodeAggregateById( - $contentStreamId, + $nodeAggregate = $contentGraph->findNodeAggregateById( $nodeAggregateId ); @@ -449,14 +437,12 @@ protected function requireProjectedNodeAggregateToNotExist( * @throws NodeAggregateCurrentlyDoesNotExist */ public function requireProjectedParentNodeAggregate( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $childNodeAggregateId, - OriginDimensionSpacePoint $childOriginDimensionSpacePoint, - ContentRepository $contentRepository + OriginDimensionSpacePoint $childOriginDimensionSpacePoint ): NodeAggregate { - $parentNodeAggregate = $contentRepository->getContentGraph() + $parentNodeAggregate = $contentGraph ->findParentNodeAggregateByChildOriginDimensionSpacePoint( - $contentStreamId, $childNodeAggregateId, $childOriginDimensionSpacePoint ); @@ -465,7 +451,7 @@ public function requireProjectedParentNodeAggregate( throw new NodeAggregateCurrentlyDoesNotExist( 'Parent node aggregate for ' . $childNodeAggregateId->value . ' does currently not exist in origin dimension space point ' . $childOriginDimensionSpacePoint->toJson() - . ' and content stream ' . $contentStreamId->value, + . ' and workspace ' . $contentGraph->getWorkspaceName()->value, 1645368685 ); } @@ -484,7 +470,7 @@ protected function requireNodeAggregateToCoverDimensionSpacePoint( throw new NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint( 'Node aggregate "' . $nodeAggregate->nodeAggregateId->value . '" does currently not cover dimension space point ' - . json_encode($dimensionSpacePoint) . '.', + . json_encode($dimensionSpacePoint, JSON_THROW_ON_ERROR) . '.', 1541678877 ); } @@ -536,10 +522,9 @@ protected function requireNodeAggregateToBeUntethered(NodeAggregate $nodeAggrega * @throws NodeAggregateIsDescendant */ protected function requireNodeAggregateToNotBeDescendant( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregate $nodeAggregate, - NodeAggregate $referenceNodeAggregate, - ContentRepository $contentRepository + NodeAggregate $referenceNodeAggregate ): void { if ($nodeAggregate->nodeAggregateId->equals($referenceNodeAggregate->nodeAggregateId)) { throw new NodeAggregateIsDescendant( @@ -549,16 +534,14 @@ protected function requireNodeAggregateToNotBeDescendant( ); } foreach ( - $contentRepository->getContentGraph()->findChildNodeAggregates( - $contentStreamId, + $contentGraph->findChildNodeAggregates( $referenceNodeAggregate->nodeAggregateId ) as $childReferenceNodeAggregate ) { $this->requireNodeAggregateToNotBeDescendant( - $contentStreamId, + $contentGraph, $nodeAggregate, - $childReferenceNodeAggregate, - $contentRepository + $childReferenceNodeAggregate ); } } @@ -567,14 +550,12 @@ protected function requireNodeAggregateToNotBeDescendant( * @throws NodeAggregateIsNoSibling */ protected function requireNodeAggregateToBeSibling( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $referenceNodeAggregateId, NodeAggregateId $siblingNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, - ContentRepository $contentRepository, ): void { - $succeedingSiblings = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $succeedingSiblings = $contentGraph->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findSucceedingSiblingNodes($referenceNodeAggregateId, FindSucceedingSiblingNodesFilter::create()); @@ -582,8 +563,7 @@ protected function requireNodeAggregateToBeSibling( return; } - $precedingSiblings = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $precedingSiblings = $contentGraph->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findPrecedingSiblingNodes($referenceNodeAggregateId, FindPrecedingSiblingNodesFilter::create()); @@ -602,14 +582,12 @@ protected function requireNodeAggregateToBeSibling( * @throws NodeAggregateIsNoChild */ protected function requireNodeAggregateToBeChild( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $childNodeAggregateId, NodeAggregateId $parentNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, - ContentRepository $contentRepository, ): void { - $childNodes = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $childNodes = $contentGraph->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() )->findChildNodes($parentNodeAggregateId, FindChildNodesFilter::create()); @@ -628,19 +606,17 @@ protected function requireNodeAggregateToBeChild( * @throws NodeNameIsAlreadyOccupied */ protected function requireNodeNameToBeUnoccupied( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, ?NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentOriginDimensionSpacePoint, - DimensionSpacePointSet $dimensionSpacePoints, - ContentRepository $contentRepository + DimensionSpacePointSet $dimensionSpacePoints ): void { if ($nodeName === null) { return; } - $dimensionSpacePointsOccupiedByChildNodeName = $contentRepository->getContentGraph() + $dimensionSpacePointsOccupiedByChildNodeName = $contentGraph ->getDimensionSpacePointsOccupiedByChildNodeName( - $contentStreamId, $nodeName, $parentNodeAggregateId, $parentOriginDimensionSpacePoint, @@ -659,17 +635,16 @@ protected function requireNodeNameToBeUnoccupied( * @throws NodeNameIsAlreadyCovered */ protected function requireNodeNameToBeUncovered( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, ?NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, - DimensionSpacePointSet $dimensionSpacePointsToBeCovered, - ContentRepository $contentRepository + DimensionSpacePointSet $dimensionSpacePointsToBeCovered ): void { if ($nodeName === null) { return; } - $childNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregatesByName( - $contentStreamId, + + $childNodeAggregates = $contentGraph->findChildNodeAggregatesByName( $parentNodeAggregateId, $nodeName ); @@ -696,7 +671,7 @@ protected function requireNodeAggregateToOccupyDimensionSpacePoint( ): void { if (!$nodeAggregate->occupiesDimensionSpacePoint($originDimensionSpacePoint)) { throw new DimensionSpacePointIsNotYetOccupied( - 'Dimension space point ' . json_encode($originDimensionSpacePoint) + 'Dimension space point ' . json_encode($originDimensionSpacePoint, JSON_THROW_ON_ERROR) . ' is not yet occupied by node aggregate "' . $nodeAggregate->nodeAggregateId->value . '"', 1552595396 ); @@ -712,7 +687,7 @@ protected function requireNodeAggregateToNotOccupyDimensionSpacePoint( ): void { if ($nodeAggregate->occupiesDimensionSpacePoint($originDimensionSpacePoint)) { throw new DimensionSpacePointIsAlreadyOccupied( - 'Dimension space point ' . json_encode($originDimensionSpacePoint) + 'Dimension space point ' . json_encode($originDimensionSpacePoint, JSON_THROW_ON_ERROR) . ' is already occupied by node aggregate "' . $nodeAggregate->nodeAggregateId->value . '"', 1552595441 ); @@ -758,10 +733,9 @@ protected function getExpectedVersionOfContentStream( ContentStreamId $contentStreamId, ContentRepository $contentRepository ): ExpectedVersion { + return ExpectedVersion::fromVersion( - $contentRepository->getContentStreamFinder() - ->findVersionForContentStream($contentStreamId) - ->unwrap() + $contentRepository->getContentStreamFinder()->findVersionForContentStream($contentStreamId)->unwrap() ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php deleted file mode 100644 index 96bf09b3a80..00000000000 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ContentStreamIdOverride.php +++ /dev/null @@ -1,67 +0,0 @@ -getWorkspaceFinder()->findOneByName($workspaceName)?->currentContentStreamId; - - if (!$contentStreamId) { - throw new ContentStreamDoesNotExistYet( - 'Content stream for workspace "' . $workspaceName->value . '" does not exist yet.', - 1710407870 - ); - } - - return $contentStreamId; - } -} diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php index 62c1f351f64..ef78ca2d0da 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeCreationInternals.php @@ -14,13 +14,12 @@ namespace Neos\ContentRepository\Core\Feature\Common; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * @internal implementation details of command handlers @@ -41,30 +40,21 @@ trait NodeCreationInternals * operates on the explicitly set succeeding sibling instead of the node itself. */ private function resolveInterdimensionalSiblingsForCreation( - ContentRepository $contentRepository, - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $requestedSucceedingSiblingNodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, DimensionSpacePointSet $coveredDimensionSpacePoints, ): InterdimensionalSiblings { - $originSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $sourceOrigin->toDimensionSpacePoint(), - VisibilityConstraints::withoutRestrictions() - ); - $originAlternativeSucceedingSiblings = $originSubgraph->findSucceedingSiblingNodes( + $subGraph = $contentGraph->getSubgraph($sourceOrigin->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions()); + $originAlternativeSucceedingSiblings = $subGraph->findSucceedingSiblingNodes( $requestedSucceedingSiblingNodeAggregateId, FindSucceedingSiblingNodesFilter::create() ); $interdimensionalSiblings = []; foreach ($coveredDimensionSpacePoints as $coveredDimensionSpacePoint) { - $variantSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $coveredDimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() - ); - $variantSucceedingSibling = $variantSubgraph->findNodeById($requestedSucceedingSiblingNodeAggregateId); + $subGraph = $contentGraph->getSubgraph($coveredDimensionSpacePoint, VisibilityConstraints::withoutRestrictions()); + $variantSucceedingSibling = $subGraph->findNodeById($requestedSucceedingSiblingNodeAggregateId); if ($variantSucceedingSibling) { // a) happy path, the explicitly requested succeeding sibling also exists in this dimension space point $interdimensionalSiblings[] = new InterdimensionalSibling( @@ -76,7 +66,7 @@ private function resolveInterdimensionalSiblingsForCreation( // check the other siblings succeeding in the origin dimension space point foreach ($originAlternativeSucceedingSiblings as $originSibling) { - $alternativeVariantSucceedingSibling = $variantSubgraph->findNodeById($originSibling->nodeAggregateId); + $alternativeVariantSucceedingSibling = $subGraph->findNodeById($originSibling->nodeAggregateId); if (!$alternativeVariantSucceedingSibling) { continue; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php index 54b8d0a60e2..08aaa79770a 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/NodeVariationInternals.php @@ -23,12 +23,12 @@ 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\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; -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\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * @internal implementation details of command handlers @@ -38,11 +38,10 @@ trait NodeVariationInternals abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\InterDimensionalVariationGraph; protected function createEventsForVariations( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events { return match ( $this->getInterDimensionalVariationGraph()->getVariantType( @@ -51,45 +50,40 @@ protected function createEventsForVariations( ) ) { DimensionSpace\VariantType::TYPE_SPECIALIZATION => $this->handleCreateNodeSpecializationVariant( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, - $nodeAggregate, - $contentRepository + $nodeAggregate ), DimensionSpace\VariantType::TYPE_GENERALIZATION => $this->handleCreateNodeGeneralizationVariant( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, - $nodeAggregate, - $contentRepository + $nodeAggregate ), default => $this->handleCreateNodePeerVariant( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, - $nodeAggregate, - $contentRepository + $nodeAggregate ), }; } protected function handleCreateNodeSpecializationVariant( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events { $specializationVisibility = $this->calculateEffectiveVisibility($targetOrigin, $nodeAggregate); $events = $this->collectNodeSpecializationVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, $nodeAggregate, $specializationVisibility, - [], - $contentRepository + [] ); return Events::fromArray($events); @@ -100,22 +94,20 @@ protected function handleCreateNodeSpecializationVariant( * @return array */ protected function collectNodeSpecializationVariantsThatWillHaveBeenCreated( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate, DimensionSpacePointSet $specializationVisibility, - array $events, - ContentRepository $contentRepository + array $events ): array { $events[] = new NodeSpecializationVariantWasCreated( - $contentStreamId, + $contentGraph->getContentStreamId(), $nodeAggregate->nodeAggregateId, $sourceOrigin, $targetOrigin, $this->resolveInterdimensionalSiblings( - $contentRepository, - $contentStreamId, + $contentGraph, $nodeAggregate->nodeAggregateId, $sourceOrigin, $specializationVisibility @@ -123,19 +115,17 @@ protected function collectNodeSpecializationVariantsThatWillHaveBeenCreated( ); foreach ( - $contentRepository->getContentGraph()->findTetheredChildNodeAggregates( - $contentStreamId, + $contentGraph->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ) as $tetheredChildNodeAggregate ) { $events = $this->collectNodeSpecializationVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, $tetheredChildNodeAggregate, $specializationVisibility, - $events, - $contentRepository + $events ); } @@ -143,21 +133,19 @@ protected function collectNodeSpecializationVariantsThatWillHaveBeenCreated( } protected function handleCreateNodeGeneralizationVariant( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events { $generalizationVisibility = $this->calculateEffectiveVisibility($targetOrigin, $nodeAggregate); $events = $this->collectNodeGeneralizationVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, $nodeAggregate, $generalizationVisibility, - [], - $contentRepository + [] ); return Events::fromArray($events); @@ -168,22 +156,20 @@ protected function handleCreateNodeGeneralizationVariant( * @return array */ protected function collectNodeGeneralizationVariantsThatWillHaveBeenCreated( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate, DimensionSpacePointSet $generalizationVisibility, - array $events, - ContentRepository $contentRepository + array $events ): array { $events[] = new NodeGeneralizationVariantWasCreated( - $contentStreamId, + $contentGraph->getContentStreamId(), $nodeAggregate->nodeAggregateId, $sourceOrigin, $targetOrigin, $this->resolveInterdimensionalSiblings( - $contentRepository, - $contentStreamId, + $contentGraph, $nodeAggregate->nodeAggregateId, $sourceOrigin, $generalizationVisibility @@ -191,19 +177,17 @@ protected function collectNodeGeneralizationVariantsThatWillHaveBeenCreated( ); foreach ( - $contentRepository->getContentGraph()->findTetheredChildNodeAggregates( - $contentStreamId, + $contentGraph->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ) as $tetheredChildNodeAggregate ) { $events = $this->collectNodeGeneralizationVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, $tetheredChildNodeAggregate, $generalizationVisibility, - $events, - $contentRepository + $events ); } @@ -211,21 +195,19 @@ protected function collectNodeGeneralizationVariantsThatWillHaveBeenCreated( } protected function handleCreateNodePeerVariant( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events { $peerVisibility = $this->calculateEffectiveVisibility($targetOrigin, $nodeAggregate); $events = $this->collectNodePeerVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, $nodeAggregate, $peerVisibility, - [], - $contentRepository + [] ); return Events::fromArray($events); @@ -236,22 +218,20 @@ protected function handleCreateNodePeerVariant( * @return array */ protected function collectNodePeerVariantsThatWillHaveBeenCreated( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, NodeAggregate $nodeAggregate, DimensionSpacePointSet $peerVisibility, - array $events, - ContentRepository $contentRepository + array $events ): array { $events[] = new NodePeerVariantWasCreated( - $contentStreamId, + $contentGraph->getContentStreamId(), $nodeAggregate->nodeAggregateId, $sourceOrigin, $targetOrigin, $this->resolveInterdimensionalSiblings( - $contentRepository, - $contentStreamId, + $contentGraph, $nodeAggregate->nodeAggregateId, $sourceOrigin, $peerVisibility @@ -259,19 +239,17 @@ protected function collectNodePeerVariantsThatWillHaveBeenCreated( ); foreach ( - $contentRepository->getContentGraph()->findTetheredChildNodeAggregates( - $contentStreamId, + $contentGraph->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ) as $tetheredChildNodeAggregate ) { $events = $this->collectNodePeerVariantsThatWillHaveBeenCreated( - $contentStreamId, + $contentGraph, $sourceOrigin, $targetOrigin, $tetheredChildNodeAggregate, $peerVisibility, - $events, - $contentRepository + $events ); } @@ -291,33 +269,20 @@ protected function collectNodePeerVariantsThatWillHaveBeenCreated( * except this operates on the to-be-varied node itself instead of an explicitly set succeeding sibling */ private function resolveInterdimensionalSiblings( - ContentRepository $contentRepository, - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $varyingNodeAggregateId, OriginDimensionSpacePoint $sourceOrigin, DimensionSpacePointSet $variantCoverage, ): InterdimensionalSiblings { - $originSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $sourceOrigin->toDimensionSpacePoint(), - VisibilityConstraints::withoutRestrictions() - ); - $originSiblings = $originSubgraph->findSucceedingSiblingNodes( - $varyingNodeAggregateId, - FindSucceedingSiblingNodesFilter::create() - ); + $originSiblings = $contentGraph + ->getSubgraph($sourceOrigin->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions()) + ->findSucceedingSiblingNodes($varyingNodeAggregateId, FindSucceedingSiblingNodesFilter::create()); $interdimensionalSiblings = []; foreach ($variantCoverage as $variantDimensionSpacePoint) { - $variantSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, - $variantDimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() - ); - // check the siblings succeeding in the origin dimension space point foreach ($originSiblings as $originSibling) { - $variantSibling = $variantSubgraph->findNodeById($originSibling->nodeAggregateId); + $variantSibling = $contentGraph->getSubgraph($variantDimensionSpacePoint, VisibilityConstraints::withoutRestrictions())->findNodeById($originSibling->nodeAggregateId); if (!$variantSibling) { continue; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php b/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php index 5d2a01fce2d..abe3e5b73ef 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/TetheredNodeInternals.php @@ -14,7 +14,6 @@ * source code. */ -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\Feature\NodeCreation\Event\NodeAggregateWithNodeWasCreated; @@ -23,6 +22,7 @@ use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; @@ -40,11 +40,10 @@ trait TetheredNodeInternals abstract protected function getPropertyConverter(): PropertyConverter; abstract protected function createEventsForVariations( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $sourceOrigin, OriginDimensionSpacePoint $targetOrigin, - NodeAggregate $nodeAggregate, - ContentRepository $contentRepository + NodeAggregate $nodeAggregate ): Events; /** @@ -56,15 +55,14 @@ abstract protected function createEventsForVariations( * @throws \Exception */ protected function createEventsForMissingTetheredNode( + ContentGraphInterface $contentGraph, NodeAggregate $parentNodeAggregate, OriginDimensionSpacePoint $originDimensionSpacePoint, NodeName $tetheredNodeName, ?NodeAggregateId $tetheredNodeAggregateId, - NodeType $expectedTetheredNodeType, - ContentRepository $contentRepository + NodeType $expectedTetheredNodeType ): Events { - $childNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregatesByName( - $parentNodeAggregate->contentStreamId, + $childNodeAggregates = $contentGraph->findChildNodeAggregatesByName( $parentNodeAggregate->nodeAggregateId, $tetheredNodeName ); @@ -149,11 +147,10 @@ protected function createEventsForMissingTetheredNode( } /** @var Node $childNodeSource Node aggregates are never empty */ return $this->createEventsForVariations( - $parentNodeAggregate->contentStreamId, + $contentGraph, $childNodeSource->originDimensionSpacePoint, $originDimensionSpacePoint, - $parentNodeAggregate, - $contentRepository + $parentNodeAggregate ); } else { throw new \RuntimeException( diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php index 86fe2b2ac22..6aac5d47e9f 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php @@ -52,7 +52,7 @@ public function canHandle(CommandInterface $command): bool public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish { return match ($command::class) { - CreateContentStream::class => $this->handleCreateContentStream($command, $contentRepository), + CreateContentStream::class => $this->handleCreateContentStream($command), CloseContentStream::class => $this->handleCloseContentStream($command, $contentRepository), ReopenContentStream::class => $this->handleReopenContentStream($command, $contentRepository), ForkContentStream::class => $this->handleForkContentStream($command, $contentRepository), @@ -65,10 +65,8 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo * @throws ContentStreamAlreadyExists */ private function handleCreateContentStream( - CreateContentStream $command, - ContentRepository $contentRepository + CreateContentStream $command ): EventsToPublish { - $this->requireContentStreamToNotExistYet($command->contentStreamId, $contentRepository); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->contentStreamId) ->getEventStreamName(); @@ -134,10 +132,9 @@ private function handleForkContentStream( ): EventsToPublish { $this->requireContentStreamToExist($command->sourceContentStreamId, $contentRepository); $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId, $contentRepository); - $this->requireContentStreamToNotExistYet($command->newContentStreamId, $contentRepository); - $sourceContentStreamVersion = $contentRepository->getContentStreamFinder() - ->findVersionForContentStream($command->sourceContentStreamId); + // TOOD: THis is not great + $sourceContentStreamVersion = $contentRepository->getContentStreamFinder()->findVersionForContentStream($command->sourceContentStreamId); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->newContentStreamId) ->getEventStreamName(); @@ -178,22 +175,6 @@ private function handleRemoveContentStream( ); } - /** - * @param ContentStreamId $contentStreamId - * @throws ContentStreamAlreadyExists - */ - protected function requireContentStreamToNotExistYet( - ContentStreamId $contentStreamId, - ContentRepository $contentRepository - ): void { - if ($contentRepository->getContentStreamFinder()->hasContentStream($contentStreamId)) { - throw new ContentStreamAlreadyExists( - 'Content stream "' . $contentStreamId->value . '" already exists.', - 1521386345 - ); - } - } - /** * @param ContentStreamId $contentStreamId * @throws ContentStreamDoesNotExistYet @@ -202,7 +183,8 @@ protected function requireContentStreamToExist( ContentStreamId $contentStreamId, ContentRepository $contentRepository ): void { - if (!$contentRepository->getContentStreamFinder()->hasContentStream($contentStreamId)) { + $maybeVersion = $contentRepository->getContentStreamFinder()->findVersionForContentStream($contentStreamId); + if ($maybeVersion->isNothing()) { throw new ContentStreamDoesNotExistYet( 'Content stream "' . $contentStreamId->value . '" does not exist yet.', 1521386692 @@ -214,7 +196,8 @@ protected function requireContentStreamToNotBeClosed( ContentStreamId $contentStreamId, ContentRepository $contentRepository ): void { - if ($contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId) === ContentStreamState::STATE_CLOSED) { + $contentStreamState = $contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId); + if ($contentStreamState === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', 1710260081 @@ -226,7 +209,8 @@ protected function requireContentStreamToBeClosed( ContentStreamId $contentStreamId, ContentRepository $contentRepository ): void { - if ($contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId) !== ContentStreamState::STATE_CLOSED) { + $contentStreamState = $contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId); + if ($contentStreamState !== ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsNotClosed( 'Content stream "' . $contentStreamId->value . '" is not closed.', 1710405911 @@ -238,9 +222,9 @@ protected function getExpectedVersionOfContentStream( ContentStreamId $contentStreamId, ContentRepository $contentRepository ): ExpectedVersion { + $maybeVersion = $contentRepository->getContentStreamFinder()->findVersionForContentStream($contentStreamId); return ExpectedVersion::fromVersion( - $contentRepository->getContentStreamFinder() - ->findVersionForContentStream($contentStreamId) + $maybeVersion ->unwrap() ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php index ca0b66861b4..1fd62aa0af7 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php @@ -33,9 +33,6 @@ use Neos\ContentRepository\Core\Feature\DimensionSpaceAdjustment\Exception\DimensionSpacePointAlreadyExists; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; -use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\EventStore\Model\EventStream\ExpectedVersion; /** @@ -67,14 +64,13 @@ private function handleMoveDimensionSpacePoint( MoveDimensionSpacePoint $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStreamForWorkspaceName($command->workspaceName, $contentRepository); - $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $streamName = ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(); self::requireDimensionSpacePointToBeEmptyInContentStream( - $command->target, - $contentStreamId, - $contentRepository->getContentGraph() + $contentGraph, + $command->target ); $this->requireDimensionSpacePointToExist($command->target); @@ -82,7 +78,7 @@ private function handleMoveDimensionSpacePoint( $streamName, Events::with( new DimensionSpacePointWasMoved( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->source, $command->target ), @@ -95,14 +91,13 @@ private function handleAddDimensionShineThrough( AddDimensionShineThrough $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStreamForWorkspaceName($command->workspaceName, $contentRepository); - $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $streamName = ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(); self::requireDimensionSpacePointToBeEmptyInContentStream( - $command->target, - $contentStreamId, - $contentRepository->getContentGraph() + $contentGraph, + $command->target ); $this->requireDimensionSpacePointToExist($command->target); @@ -112,7 +107,7 @@ private function handleAddDimensionShineThrough( $streamName, Events::with( new DimensionShineThroughWasAdded( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->source, $command->target ) @@ -133,19 +128,14 @@ protected function requireDimensionSpacePointToExist(DimensionSpacePoint $dimens } private static function requireDimensionSpacePointToBeEmptyInContentStream( - DimensionSpacePoint $dimensionSpacePoint, - ContentStreamId $contentStreamId, - ContentGraphInterface $contentGraph + ContentGraphInterface $contentGraph, + DimensionSpacePoint $dimensionSpacePoint ): void { - $subgraph = $contentGraph->getSubgraph( - $contentStreamId, - $dimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() - ); - if ($subgraph->countNodes() > 0) { + $hasNodes = $contentGraph->getSubgraph($dimensionSpacePoint, VisibilityConstraints::withoutRestrictions())->countNodes(); + if ($hasNodes > 0) { throw new DimensionSpacePointAlreadyExists(sprintf( 'the content stream %s already contained nodes in dimension space point %s - this is not allowed.', - $contentStreamId->value, + $contentGraph->getContentStreamId()->value, $dimensionSpacePoint->toJson(), ), 1612898126); } @@ -164,23 +154,4 @@ private function requireDimensionSpacePointToBeSpecialization( throw DimensionSpacePointIsNoSpecialization::butWasSupposedToBe($target, $source); } } - - /** - * @throws ContentStreamDoesNotExistYet - */ - protected function requireContentStreamForWorkspaceName( - WorkspaceName $workspaceName, - ContentRepository $contentRepository - ): ContentStreamId { - $contentStreamId = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName) - ?->currentContentStreamId; - if (!$contentStreamId || !$contentRepository->getContentStreamFinder()->hasContentStream($contentStreamId)) { - throw new ContentStreamDoesNotExistYet( - 'Content stream "' . $contentStreamId?->value . '" does not exist yet.', - 1521386692 - ); - } - - return $contentStreamId; - } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php index 27b49905cb6..ee9bb692ad3 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php @@ -52,6 +52,9 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\SubtreeTagging; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; +use Neos\ContentRepository\Core\Projection\ContentStream\ContentStreamFinder; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * @internal from userland, you'll use ContentRepository::handle to dispatch commands @@ -72,41 +75,19 @@ final class NodeAggregateCommandHandler implements CommandHandlerInterface use NodeVariation; use TetheredNodeInternals; - /** - * Used for constraint checks against the current outside configuration state of node types - */ - private NodeTypeManager $nodeTypeManager; - - /** - * Used for variation resolution from the current outside state of content dimensions - */ - private DimensionSpace\InterDimensionalVariationGraph $interDimensionalVariationGraph; - - /** - * Used for constraint checks against the current outside configuration state of content dimensions - */ - private DimensionSpace\ContentDimensionZookeeper $contentDimensionZookeeper; - - protected PropertyConverter $propertyConverter; - /** * can be disabled in {@see NodeAggregateCommandHandler::withoutAncestorNodeTypeConstraintChecks()} */ private bool $ancestorNodeTypeConstraintChecksEnabled = true; public function __construct( - NodeTypeManager $nodeTypeManager, - DimensionSpace\ContentDimensionZookeeper $contentDimensionZookeeper, - DimensionSpace\InterDimensionalVariationGraph $interDimensionalVariationGraph, - PropertyConverter $propertyConverter + private readonly NodeTypeManager $nodeTypeManager, + private readonly DimensionSpace\ContentDimensionZookeeper $contentDimensionZookeeper, + private readonly DimensionSpace\InterDimensionalVariationGraph $interDimensionalVariationGraph, + private readonly PropertyConverter $propertyConverter, ) { - $this->nodeTypeManager = $nodeTypeManager; - $this->contentDimensionZookeeper = $contentDimensionZookeeper; - $this->interDimensionalVariationGraph = $interDimensionalVariationGraph; - $this->propertyConverter = $propertyConverter; } - public function canHandle(CommandInterface $command): bool { return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); @@ -118,22 +99,22 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo return match ($command::class) { SetNodeProperties::class => $this->handleSetNodeProperties($command, $contentRepository), SetSerializedNodeProperties::class - => $this->handleSetSerializedNodeProperties($command, $contentRepository), + => $this->handleSetSerializedNodeProperties($command, $contentRepository), SetNodeReferences::class => $this->handleSetNodeReferences($command, $contentRepository), SetSerializedNodeReferences::class - => $this->handleSetSerializedNodeReferences($command, $contentRepository), + => $this->handleSetSerializedNodeReferences($command, $contentRepository), ChangeNodeAggregateType::class => $this->handleChangeNodeAggregateType($command, $contentRepository), RemoveNodeAggregate::class => $this->handleRemoveNodeAggregate($command, $contentRepository), CreateNodeAggregateWithNode::class - => $this->handleCreateNodeAggregateWithNode($command, $contentRepository), + => $this->handleCreateNodeAggregateWithNode($command, $contentRepository), CreateNodeAggregateWithNodeAndSerializedProperties::class - => $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($command, $contentRepository), + => $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($command, $contentRepository), MoveNodeAggregate::class => $this->handleMoveNodeAggregate($command, $contentRepository), CreateNodeVariant::class => $this->handleCreateNodeVariant($command, $contentRepository), CreateRootNodeAggregateWithNode::class - => $this->handleCreateRootNodeAggregateWithNode($command, $contentRepository), + => $this->handleCreateRootNodeAggregateWithNode($command, $contentRepository), UpdateRootNodeAggregateDimensions::class - => $this->handleUpdateRootNodeAggregateDimensions($command, $contentRepository), + => $this->handleUpdateRootNodeAggregateDimensions($command, $contentRepository), DisableNodeAggregate::class => $this->handleDisableNodeAggregate($command, $contentRepository), EnableNodeAggregate::class => $this->handleEnableNodeAggregate($command, $contentRepository), TagSubtree::class => $this->handleTagSubtree($command, $contentRepository), diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index 72ecce321d3..3f288366ff2 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -34,6 +34,7 @@ use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; @@ -42,7 +43,6 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Node\PropertyName; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * @internal implementation detail of Command Handlers @@ -126,8 +126,9 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( CreateNodeAggregateWithNodeAndSerializedProperties $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $this->requireContentStream($command->workspaceName, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); $this->requireDimensionSpacePointToExist($command->originDimensionSpacePoint->toDimensionSpacePoint()); $nodeType = $this->requireNodeType($command->nodeTypeName); $this->requireNodeTypeToNotBeAbstract($nodeType); @@ -136,28 +137,24 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $this->requireTetheredDescendantNodeTypesToNotBeOfTypeRoot($nodeType); if ($this->areAncestorNodeTypeConstraintChecksEnabled()) { $this->requireConstraintsImposedByAncestorsAreMet( - $contentStreamId, + $contentGraph, $nodeType, $command->nodeName, - [$command->parentNodeAggregateId], - $contentRepository + [$command->parentNodeAggregateId] ); } $this->requireProjectedNodeAggregateToNotExist( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $parentNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->parentNodeAggregateId, - $contentRepository + $contentGraph, + $command->parentNodeAggregateId ); if ($command->succeedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->succeedingSiblingNodeAggregateId, - $contentRepository + $contentGraph, + $command->succeedingSiblingNodeAggregateId ); } $this->requireNodeAggregateToCoverDimensionSpacePoint( @@ -172,14 +169,13 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( ); if ($command->nodeName) { $this->requireNodeNameToBeUnoccupied( - $contentStreamId, + $contentGraph, $command->nodeName, $command->parentNodeAggregateId, $parentNodeAggregate->classification->isRoot() ? DimensionSpace\OriginDimensionSpacePoint::createWithoutDimensions() : $command->originDimensionSpacePoint, $coveredDimensionSpacePoints, - $contentRepository ); } @@ -195,9 +191,8 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $descendantNodeAggregateIds->getNodeAggregateIds() as $descendantNodeAggregateId ) { $this->requireProjectedNodeAggregateToNotExist( - $contentStreamId, - $descendantNodeAggregateId, - $contentRepository + $contentGraph, + $descendantNodeAggregateId ); } @@ -206,26 +201,25 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( $events = [ $this->createRegularWithNode( + $contentGraph, $command, - $contentStreamId, $coveredDimensionSpacePoints, - $initialPropertyValues, - $contentRepository + $initialPropertyValues ) ]; array_push($events, ...iterator_to_array($this->handleTetheredChildNodes( $command, - $contentStreamId, + $contentGraph, $nodeType, $coveredDimensionSpacePoints, $command->nodeAggregateId, $descendantNodeAggregateIds, - null, + null ))); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand($command, Events::fromArray($events)), $expectedVersion @@ -233,21 +227,19 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( } private function createRegularWithNode( + ContentGraphInterface $contentGraph, CreateNodeAggregateWithNodeAndSerializedProperties $command, - ContentStreamId $contentStreamId, DimensionSpacePointSet $coveredDimensionSpacePoints, SerializedPropertyValues $initialPropertyValues, - ContentRepository $contentRepository, ): NodeAggregateWithNodeWasCreated { return new NodeAggregateWithNodeWasCreated( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $command->nodeTypeName, $command->originDimensionSpacePoint, $command->succeedingSiblingNodeAggregateId ? $this->resolveInterdimensionalSiblingsForCreation( - $contentRepository, - $contentStreamId, + $contentGraph, $command->succeedingSiblingNodeAggregateId, $command->originDimensionSpacePoint, $coveredDimensionSpacePoints @@ -266,12 +258,12 @@ private function createRegularWithNode( */ private function handleTetheredChildNodes( CreateNodeAggregateWithNodeAndSerializedProperties $command, - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeType $nodeType, DimensionSpacePointSet $coveredDimensionSpacePoints, NodeAggregateId $parentNodeAggregateId, NodeAggregateIdsByNodePaths $nodeAggregateIds, - ?NodePath $nodePath, + ?NodePath $nodePath ): Events { $events = []; foreach ($this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($nodeType) as $rawNodeName => $childNodeType) { @@ -285,7 +277,7 @@ private function handleTetheredChildNodes( $initialPropertyValues = SerializedPropertyValues::defaultFromNodeType($childNodeType, $this->getPropertyConverter()); $events[] = new NodeAggregateWithNodeWasCreated( - $contentStreamId, + $contentGraph->getContentStreamId(), $childNodeAggregateId, $childNodeType->name, $command->originDimensionSpacePoint, @@ -298,12 +290,12 @@ private function handleTetheredChildNodes( array_push($events, ...iterator_to_array($this->handleTetheredChildNodes( $command, - $contentStreamId, + $contentGraph, $childNodeType, $coveredDimensionSpacePoints, $childNodeAggregateId, $nodeAggregateIds, - $childNodePath, + $childNodePath ))); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php index eca096a52c7..8c673fd535e 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php @@ -26,9 +26,11 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasTagged; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasUntagged; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateCurrentlyDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * @internal implementation detail of Command Handlers @@ -38,23 +40,21 @@ trait NodeDisabling abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\InterDimensionalVariationGraph; /** + * @param DisableNodeAggregate $command + * @param ContentRepository $contentRepository * @return EventsToPublish - * @throws ContentStreamDoesNotExistYet - * @throws DimensionSpacePointNotFound - * @throws NodeAggregateCurrentlyDoesNotExist - * @throws NodeAggregatesTypeIsAmbiguous */ private function handleDisableNodeAggregate( DisableNodeAggregate $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $this->requireContentStream($command->workspaceName, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, @@ -74,7 +74,7 @@ private function handleDisableNodeAggregate( $events = Events::with( new SubtreeWasTagged( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $affectedDimensionSpacePoints, SubtreeTag::disabled(), @@ -82,7 +82,7 @@ private function handleDisableNodeAggregate( ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, @@ -103,13 +103,12 @@ public function handleEnableNodeAggregate( EnableNodeAggregate $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, @@ -129,7 +128,7 @@ public function handleEnableNodeAggregate( $events = Events::with( new SubtreeWasUntagged( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $affectedDimensionSpacePoints, SubtreeTag::disabled(), @@ -137,7 +136,7 @@ public function handleEnableNodeAggregate( ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId())->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand($command, $events), $expectedVersion ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php index acac660dfbd..a069297ea90 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php @@ -14,6 +14,8 @@ namespace Neos\ContentRepository\Core\Feature\NodeDuplication; +use Neos\ContentRepository\Core\NodeType\NodeType; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\ContentRepository; @@ -35,7 +37,7 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeConstraintException; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * @internal from userland, you'll use ContentRepository::handle to dispatch commands @@ -52,6 +54,11 @@ public function __construct( ) { } + protected function getContentGraph(WorkspaceName $workspaceName, ContentRepository $contentRepository): ContentGraphInterface + { + return $contentRepository->getContentGraph($workspaceName); + } + protected function getNodeTypeManager(): NodeTypeManager { return $this->nodeTypeManager; @@ -83,8 +90,8 @@ private function handleCopyNodesRecursively( ContentRepository $contentRepository ): EventsToPublish { // Basic constraints (Content Stream / Dimension Space Point / Node Type of to-be-inserted root node) - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); $this->requireDimensionSpacePointToExist( $command->targetDimensionSpacePoint->toDimensionSpacePoint() ); @@ -95,31 +102,27 @@ private function handleCopyNodesRecursively( // NOTE: we only check this for the *root* node of the to-be-inserted structure; and not for its // children (as we want to create the structure as-is; assuming it was already valid beforehand). $this->requireConstraintsImposedByAncestorsAreMet( - $contentStreamId, + $contentGraph, $nodeType, $command->targetNodeName, - [$command->targetParentNodeAggregateId], - $contentRepository + [$command->targetParentNodeAggregateId] ); // Constraint: The new nodeAggregateIds are not allowed to exist yet. $this->requireNewNodeAggregateIdsToNotExist( - $contentStreamId, - $command->nodeAggregateIdMapping, - $contentRepository + $contentGraph, + $command->nodeAggregateIdMapping ); // Constraint: the parent node must exist in the command's DimensionSpacePoint as well $parentNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->targetParentNodeAggregateId, - $contentRepository + $contentGraph, + $command->targetParentNodeAggregateId ); if ($command->targetSucceedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->targetSucceedingSiblingNodeAggregateId, - $contentRepository + $contentGraph, + $command->targetSucceedingSiblingNodeAggregateId ); } $this->requireNodeAggregateToCoverDimensionSpacePoint( @@ -139,21 +142,20 @@ private function handleCopyNodesRecursively( // Constraint: The node name must be free in all these dimension space points if ($command->targetNodeName) { $this->requireNodeNameToBeUnoccupied( - $contentStreamId, + $contentGraph, $command->targetNodeName, $command->targetParentNodeAggregateId, $parentNodeAggregate->classification->isRoot() ? OriginDimensionSpacePoint::createWithoutDimensions() : $command->targetDimensionSpacePoint, $coveredDimensionSpacePoints, - $contentRepository ); } // Now, we can start creating the recursive structure. $events = []; $this->createEventsForNodeToInsert( - $contentStreamId, + $contentGraph, $command->targetDimensionSpacePoint, $coveredDimensionSpacePoints, $command->targetParentNodeAggregateId, @@ -161,13 +163,12 @@ private function handleCopyNodesRecursively( $command->targetNodeName, $command->nodeTreeToInsert, $command->nodeAggregateIdMapping, - $contentRepository, $events ); return new EventsToPublish( ContentStreamEventStreamName::fromContentStreamId( - $contentStreamId + $contentGraph->getContentStreamId() )->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, @@ -178,15 +179,13 @@ private function handleCopyNodesRecursively( } private function requireNewNodeAggregateIdsToNotExist( - ContentStreamId $contentStreamId, - Dto\NodeAggregateIdMapping $nodeAggregateIdMapping, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph, + Dto\NodeAggregateIdMapping $nodeAggregateIdMapping ): void { foreach ($nodeAggregateIdMapping->getAllNewNodeAggregateIds() as $nodeAggregateId) { $this->requireProjectedNodeAggregateToNotExist( - $contentStreamId, - $nodeAggregateId, - $contentRepository + $contentGraph, + $nodeAggregateId ); } } @@ -195,7 +194,7 @@ private function requireNewNodeAggregateIdsToNotExist( * @param array $events */ private function createEventsForNodeToInsert( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, OriginDimensionSpacePoint $originDimensionSpacePoint, DimensionSpacePointSet $coveredDimensionSpacePoints, NodeAggregateId $targetParentNodeAggregateId, @@ -203,11 +202,10 @@ private function createEventsForNodeToInsert( ?NodeName $targetNodeName, NodeSubtreeSnapshot $nodeToInsert, Dto\NodeAggregateIdMapping $nodeAggregateIdMapping, - ContentRepository $contentRepository, array &$events, ): void { $events[] = new NodeAggregateWithNodeWasCreated( - $contentStreamId, + $contentGraph->getContentStreamId(), $nodeAggregateIdMapping->getNewNodeAggregateId( $nodeToInsert->nodeAggregateId ) ?: NodeAggregateId::create(), @@ -215,8 +213,7 @@ private function createEventsForNodeToInsert( $originDimensionSpacePoint, $targetSucceedingSiblingNodeAggregateId ? $this->resolveInterdimensionalSiblingsForCreation( - $contentRepository, - $contentStreamId, + $contentGraph, $targetSucceedingSiblingNodeAggregateId, $originDimensionSpacePoint, $coveredDimensionSpacePoints @@ -230,7 +227,7 @@ private function createEventsForNodeToInsert( foreach ($nodeToInsert->childNodes as $childNodeToInsert) { $this->createEventsForNodeToInsert( - $contentStreamId, + $contentGraph, $originDimensionSpacePoint, $coveredDimensionSpacePoints, // the just-inserted node becomes the new parent node ID @@ -242,7 +239,6 @@ private function createEventsForNodeToInsert( $childNodeToInsert->nodeName, $childNodeToInsert, $nodeAggregateIdMapping, - $contentRepository, $events ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php index 49d71efa330..d331a28d86c 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php @@ -26,10 +26,10 @@ use Neos\ContentRepository\Core\Feature\NodeModification\Event\NodePropertiesWereSet; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\PropertyNames; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * @internal implementation detail of Command Handlers @@ -39,21 +39,20 @@ trait NodeModification abstract protected function requireNodeType(NodeTypeName $nodeTypeName): NodeType; abstract protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph, + NodeAggregateId $nodeAggregateId ): NodeAggregate; private function handleSetNodeProperties( SetNodeProperties $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); + $this->requireContentStream($command->workspaceName, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); $this->requireDimensionSpacePointToExist($command->originDimensionSpacePoint->toDimensionSpacePoint()); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($nodeAggregate); $nodeTypeName = $nodeAggregate->nodeTypeName; @@ -78,13 +77,12 @@ private function handleSetSerializedNodeProperties( SetSerializedNodeProperties $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); // Check if node exists $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $nodeType = $this->requireNodeType($nodeAggregate->nodeTypeName); $this->requireNodeAggregateToOccupyDimensionSpacePoint($nodeAggregate, $command->originDimensionSpacePoint); @@ -98,7 +96,7 @@ private function handleSetSerializedNodeProperties( ); foreach ($affectedOrigins as $affectedOrigin) { $events[] = new NodePropertiesWereSet( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $affectedOrigin, $nodeAggregate->getCoverageByOccupant($affectedOrigin), @@ -117,7 +115,7 @@ private function handleSetSerializedNodeProperties( ); foreach ($affectedOrigins as $affectedOrigin) { $events[] = new NodePropertiesWereSet( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $affectedOrigin, $nodeAggregate->getCoverageByOccupant($affectedOrigin), @@ -130,7 +128,7 @@ private function handleSetSerializedNodeProperties( $events = $this->mergeSplitEvents($events); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, @@ -152,7 +150,7 @@ private function splitPropertiesToUnsetByScope(PropertyNames $propertiesToUnset, } return array_map( - fn(array $propertyValues): PropertyNames => PropertyNames::fromArray($propertyValues), + static fn(array $propertyValues): PropertyNames => PropertyNames::fromArray($propertyValues), $propertiesToUnsetByScope ); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index b164a54d2a0..d5d1e59627b 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -28,6 +28,7 @@ use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeMove\Dto\RelationDistributionStrategy; use Neos\ContentRepository\Core\Feature\NodeMove\Event\NodeAggregateWasMoved; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindPrecedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindSucceedingSiblingNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Pagination\Pagination; @@ -52,25 +53,22 @@ abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\ abstract protected function areAncestorNodeTypeConstraintChecksEnabled(): bool; abstract protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository ): NodeAggregate; abstract protected function requireNodeAggregateToBeSibling( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $referenceNodeAggregateId, NodeAggregateId $siblingNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, - ContentRepository $contentRepository, ): void; abstract protected function requireNodeAggregateToBeChild( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeAggregateId $childNodeAggregateId, - NodeAggregateId $parentNodAggregateId, + NodeAggregateId $parentNodeAggregateId, DimensionSpacePoint $dimensionSpacePoint, - ContentRepository $contentRepository, ): void; /** @@ -86,13 +84,13 @@ private function handleMoveNodeAggregate( MoveNodeAggregate $command, ContentRepository $contentRepository ): EventsToPublish { + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); $this->requireDimensionSpacePointToExist($command->dimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraph, $command->nodeAggregateId, - $contentRepository ); $this->requireNodeAggregateToNotBeRoot($nodeAggregate); $this->requireNodeAggregateToBeUntethered($nodeAggregate); @@ -106,27 +104,24 @@ private function handleMoveNodeAggregate( if ($command->newParentNodeAggregateId) { $this->requireConstraintsImposedByAncestorsAreMet( - $contentStreamId, + $contentGraph, $this->requireNodeType($nodeAggregate->nodeTypeName), $nodeAggregate->nodeName, [$command->newParentNodeAggregateId], - $contentRepository ); $newParentNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraph, $command->newParentNodeAggregateId, - $contentRepository ); $this->requireNodeNameToBeUncovered( - $contentStreamId, + $contentGraph, $nodeAggregate->nodeName, $command->newParentNodeAggregateId, // We need to check all covered DSPs of the parent node aggregate to prevent siblings // with different node aggregate IDs but the same name $newParentNodeAggregate->coveredDimensionSpacePoints, - $contentRepository ); $this->requireNodeAggregateToCoverDimensionSpacePoints( @@ -135,58 +130,51 @@ private function handleMoveNodeAggregate( ); $this->requireNodeAggregateToNotBeDescendant( - $contentStreamId, + $contentGraph, $newParentNodeAggregate, $nodeAggregate, - $contentRepository ); } if ($command->newPrecedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraph, $command->newPrecedingSiblingNodeAggregateId, - $contentRepository ); if ($command->newParentNodeAggregateId) { $this->requireNodeAggregateToBeChild( - $contentStreamId, + $contentGraph, $command->newPrecedingSiblingNodeAggregateId, $command->newParentNodeAggregateId, $command->dimensionSpacePoint, - $contentRepository ); } else { $this->requireNodeAggregateToBeSibling( - $contentStreamId, + $contentGraph, $command->nodeAggregateId, $command->newPrecedingSiblingNodeAggregateId, $command->dimensionSpacePoint, - $contentRepository ); } } if ($command->newSucceedingSiblingNodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, + $contentGraph, $command->newSucceedingSiblingNodeAggregateId, - $contentRepository ); if ($command->newParentNodeAggregateId) { $this->requireNodeAggregateToBeChild( - $contentStreamId, + $contentGraph, $command->newSucceedingSiblingNodeAggregateId, $command->newParentNodeAggregateId, $command->dimensionSpacePoint, - $contentRepository ); } else { $this->requireNodeAggregateToBeSibling( - $contentStreamId, + $contentGraph, $command->nodeAggregateId, $command->newSucceedingSiblingNodeAggregateId, $command->dimensionSpacePoint, - $contentRepository ); } } @@ -197,7 +185,7 @@ private function handleMoveNodeAggregate( $command->nodeAggregateId, $command->newParentNodeAggregateId, $this->resolveInterdimensionalSiblingsForMove( - $contentStreamId, + $contentGraph, $command->dimensionSpacePoint, $affectedDimensionSpacePoints, $command->nodeAggregateId, @@ -205,8 +193,7 @@ private function handleMoveNodeAggregate( $command->newSucceedingSiblingNodeAggregateId, $command->newPrecedingSiblingNodeAggregateId, $command->newParentNodeAggregateId !== null - || $command->newSucceedingSiblingNodeAggregateId === null && $command->newPrecedingSiblingNodeAggregateId === null, - $contentRepository + || ($command->newSucceedingSiblingNodeAggregateId === null) && ($command->newPrecedingSiblingNodeAggregateId === null), ) ) ); @@ -250,7 +237,7 @@ private function resolveAffectedDimensionSpacePointSet( * False when no new parent is set, which will result in the node not being moved */ private function resolveInterdimensionalSiblingsForMove( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, DimensionSpacePoint $selectedDimensionSpacePoint, DimensionSpacePointSet $affectedDimensionSpacePoints, NodeAggregateId $nodeAggregateId, @@ -258,10 +245,8 @@ private function resolveInterdimensionalSiblingsForMove( ?NodeAggregateId $succeedingSiblingId, ?NodeAggregateId $precedingSiblingId, bool $completeSet, - ContentRepository $contentRepository, ): InterdimensionalSiblings { - $selectedSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $selectedSubgraph = $contentGraph->getSubgraph( $selectedDimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); @@ -280,8 +265,7 @@ private function resolveInterdimensionalSiblingsForMove( $interdimensionalSiblings = []; foreach ($affectedDimensionSpacePoints as $dimensionSpacePoint) { - $variantSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $variantSubgraph = $contentGraph->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php index 507e5c6bb6c..6d1cd7c06e5 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php @@ -26,9 +26,9 @@ use Neos\ContentRepository\Core\Feature\NodeReferencing\Dto\NodeReferenceToWrite; use Neos\ContentRepository\Core\Feature\NodeReferencing\Dto\SerializedNodeReference; use Neos\ContentRepository\Core\Feature\NodeReferencing\Event\NodeReferencesWereSet; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * @internal implementation detail of Command Handlers @@ -38,9 +38,8 @@ trait NodeReferencing use ConstraintChecks; abstract protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph, + NodeAggregateId $nodeAggregateId ): NodeAggregate; @@ -48,12 +47,12 @@ private function handleSetNodeReferences( SetNodeReferences $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); + $this->requireContentStream($command->workspaceName, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); $this->requireDimensionSpacePointToExist($command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint()); $sourceNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->sourceNodeAggregateId, - $contentRepository + $contentGraph, + $command->sourceNodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($sourceNodeAggregate); $nodeTypeName = $sourceNodeAggregate->nodeTypeName; @@ -98,15 +97,14 @@ private function handleSetSerializedNodeReferences( SetSerializedNodeReferences $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); $this->requireDimensionSpacePointToExist( $command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint() ); $sourceNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->sourceNodeAggregateId, - $contentRepository + $contentGraph, + $command->sourceNodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($sourceNodeAggregate); $this->requireNodeAggregateToOccupyDimensionSpacePoint( @@ -124,9 +122,8 @@ private function handleSetSerializedNodeReferences( foreach ($command->references as $reference) { assert($reference instanceof SerializedNodeReference); $destinationNodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $reference->targetNodeAggregateId, - $contentRepository + $contentGraph, + $reference->targetNodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($destinationNodeAggregate); $this->requireNodeAggregateToCoverDimensionSpacePoint( @@ -152,7 +149,7 @@ private function handleSetSerializedNodeReferences( $events = Events::with( new NodeReferencesWereSet( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->sourceNodeAggregateId, $affectedOrigins, $command->referenceName, @@ -161,7 +158,7 @@ private function handleSetSerializedNodeReferences( ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/Command/RemoveNodeAggregate.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/Command/RemoveNodeAggregate.php index f169088ff73..085af255b8c 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/Command/RemoveNodeAggregate.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/Command/RemoveNodeAggregate.php @@ -21,7 +21,6 @@ use Neos\ContentRepository\Core\Feature\WorkspacePublication\Dto\NodeIdToPublishOrDiscard; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php index 446f7b58e45..edd3d2bc919 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php @@ -49,12 +49,12 @@ private function handleRemoveNodeAggregate( RemoveNodeAggregate $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $this->requireContentStream($command->workspaceName, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $this->requireNodeAggregateNotToBeTethered($nodeAggregate); @@ -64,15 +64,14 @@ private function handleRemoveNodeAggregate( ); if ($command->removalAttachmentPoint instanceof NodeAggregateId) { $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->removalAttachmentPoint, - $contentRepository + $contentGraph, + $command->removalAttachmentPoint ); } $events = Events::with( new NodeAggregateWasRemoved( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $command->nodeVariantSelectionStrategy->resolveAffectedOriginDimensionSpacePoints( $nodeAggregate->getOccupationByCovered($command->coveredDimensionSpacePoint), @@ -89,7 +88,7 @@ private function handleRemoveNodeAggregate( ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php index 40403f523ba..ea378e1e230 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php @@ -32,38 +32,36 @@ trait NodeRenaming private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command, ContentRepository $contentRepository): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $this->requireNodeAggregateToNotBeRoot($nodeAggregate, 'and Root Node Aggregates cannot be renamed'); $this->requireNodeAggregateToBeUntethered($nodeAggregate); - foreach ($contentRepository->getContentGraph()->findParentNodeAggregates($contentStreamId, $command->nodeAggregateId) as $parentNodeAggregate) { + foreach ($contentGraph->findParentNodeAggregates($command->nodeAggregateId) as $parentNodeAggregate) { foreach ($parentNodeAggregate->occupiedDimensionSpacePoints as $occupiedParentDimensionSpacePoint) { $this->requireNodeNameToBeUnoccupied( - $contentStreamId, + $contentGraph, $command->newNodeName, $parentNodeAggregate->nodeAggregateId, $occupiedParentDimensionSpacePoint, - $parentNodeAggregate->coveredDimensionSpacePoints, - $contentRepository + $parentNodeAggregate->coveredDimensionSpacePoints ); } } $events = Events::with( new NodeAggregateNameWasChanged( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $command->newNodeName, ), ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId())->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, $events diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php index 25e60f27449..2c49ced70d5 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php @@ -28,6 +28,7 @@ use Neos\ContentRepository\Core\Feature\NodeTypeChange\Event\NodeAggregateTypeWasChanged; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; @@ -38,7 +39,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\Workspace\ContentStreamId; /** @codingStandardsIgnoreStart */ /** @codingStandardsIgnoreEnd */ @@ -51,17 +51,15 @@ trait NodeTypeChange abstract protected function getNodeTypeManager(): NodeTypeManager; abstract protected function requireProjectedNodeAggregate( - ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - ContentRepository $contentRepository + ContentGraphInterface $contentRepository, + NodeAggregateId $nodeAggregateId ): NodeAggregate; abstract protected function requireConstraintsImposedByAncestorsAreMet( - ContentStreamId $contentStreamId, + ContentGraphInterface $contentGraph, NodeType $nodeType, ?NodeName $nodeName, - array $parentNodeAggregateIds, - ContentRepository $contentRepository + array $parentNodeAggregateIds ): void; abstract protected function requireNodeTypeConstraintsImposedByParentToBeMet( @@ -89,12 +87,12 @@ abstract protected function areNodeTypeConstraintsImposedByGrandparentValid( ): bool; abstract protected function createEventsForMissingTetheredNode( + ContentGraphInterface $contentGraph, NodeAggregate $parentNodeAggregate, OriginDimensionSpacePoint $originDimensionSpacePoint, NodeName $tetheredNodeName, NodeAggregateId $tetheredNodeAggregateId, - NodeType $expectedTetheredNodeType, - ContentRepository $contentRepository + NodeType $expectedTetheredNodeType ): Events; /** @@ -102,6 +100,7 @@ abstract protected function createEventsForMissingTetheredNode( * @throws NodeConstraintException * @throws NodeTypeNotFoundException * @throws NodeAggregatesTypeIsAmbiguous + * @throws \Exception */ private function handleChangeNodeAggregateType( ChangeNodeAggregateType $command, @@ -111,13 +110,12 @@ private function handleChangeNodeAggregateType( * Constraint checks **************/ // existence of content stream, node type and node aggregate - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); $newNodeType = $this->requireNodeType($command->newNodeTypeName); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); // node type detail checks @@ -126,25 +124,23 @@ private function handleChangeNodeAggregateType( $this->requireTetheredDescendantNodeTypesToNotBeOfTypeRoot($newNodeType); // the new node type must be allowed at this position in the tree - $parentNodeAggregates = $contentRepository->getContentGraph()->findParentNodeAggregates( - $nodeAggregate->contentStreamId, + $parentNodeAggregates = $contentGraph->findParentNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($parentNodeAggregates as $parentNodeAggregate) { assert($parentNodeAggregate instanceof NodeAggregate); $this->requireConstraintsImposedByAncestorsAreMet( - $contentStreamId, + $contentGraph, $newNodeType, $nodeAggregate->nodeName, - [$parentNodeAggregate->nodeAggregateId], - $contentRepository + [$parentNodeAggregate->nodeAggregateId] ); } /** @codingStandardsIgnoreStart */ match ($command->strategy) { NodeAggregateTypeChangeChildConstraintConflictResolutionStrategy::STRATEGY_HAPPY_PATH - => $this->requireConstraintsImposedByHappyPathStrategyAreMet($nodeAggregate, $newNodeType, $contentRepository), + => $this->requireConstraintsImposedByHappyPathStrategyAreMet($contentGraph, $nodeAggregate, $newNodeType), NodeAggregateTypeChangeChildConstraintConflictResolutionStrategy::STRATEGY_DELETE => null }; /** @codingStandardsIgnoreStop */ @@ -165,7 +161,7 @@ private function handleChangeNodeAggregateType( **************/ $events = [ new NodeAggregateTypeWasChanged( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $command->newNodeTypeName ), @@ -174,14 +170,14 @@ private function handleChangeNodeAggregateType( // remove disallowed nodes if ($command->strategy === NodeAggregateTypeChangeChildConstraintConflictResolutionStrategy::STRATEGY_DELETE) { array_push($events, ...iterator_to_array($this->deleteDisallowedNodesWhenChangingNodeType( + $contentGraph, $nodeAggregate, - $newNodeType, - $contentRepository + $newNodeType ))); array_push($events, ...iterator_to_array($this->deleteObsoleteTetheredNodesWhenChangingNodeType( + $contentGraph, $nodeAggregate, - $newNodeType, - $contentRepository + $newNodeType ))); } @@ -192,33 +188,32 @@ private function handleChangeNodeAggregateType( foreach ($expectedTetheredNodes as $serializedTetheredNodeName => $expectedTetheredNodeType) { $tetheredNodeName = NodeName::fromString($serializedTetheredNodeName); - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $node->subgraphIdentity->contentStreamId, + $tetheredNode = $contentGraph->getSubgraph( $node->originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() - ); - $tetheredNode = $subgraph->findNodeByPath( + )->findNodeByPath( $tetheredNodeName, - $node->nodeAggregateId + $node->nodeAggregateId, ); + if ($tetheredNode === null) { $tetheredNodeAggregateId = $command->tetheredDescendantNodeAggregateIds ->getNodeAggregateId(NodePath::fromString($tetheredNodeName->value)) ?: NodeAggregateId::create(); array_push($events, ...iterator_to_array($this->createEventsForMissingTetheredNode( + $contentGraph, $nodeAggregate, $node->originDimensionSpacePoint, $tetheredNodeName, $tetheredNodeAggregateId, - $expectedTetheredNodeType, - $contentRepository + $expectedTetheredNodeType ))); } } } return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId())->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, Events::fromArray($events), @@ -234,14 +229,13 @@ private function handleChangeNodeAggregateType( * @throws NodeConstraintException|NodeTypeNotFoundException */ private function requireConstraintsImposedByHappyPathStrategyAreMet( + ContentGraphInterface $contentGraph, NodeAggregate $nodeAggregate, - NodeType $newNodeType, - ContentRepository $contentRepository + NodeType $newNodeType ): void { // if we have children, we need to check whether they are still allowed // after we changed the node type of the $nodeAggregate to $newNodeType. - $childNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregates( - $nodeAggregate->contentStreamId, + $childNodeAggregates = $contentGraph->findChildNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($childNodeAggregates as $childNodeAggregate) { @@ -256,11 +250,9 @@ private function requireConstraintsImposedByHappyPathStrategyAreMet( // we do not need to test for grandparents here, as we did not modify the grandparents. // Thus, if it was allowed before, it is allowed now. - // additionally, we need to look one level down to the grandchildren as well // - as it could happen that these are affected by our constraint checks as well. - $grandchildNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregates( - $childNodeAggregate->contentStreamId, + $grandchildNodeAggregates = $contentGraph->findChildNodeAggregates( $childNodeAggregate->nodeAggregateId ); foreach ($grandchildNodeAggregates as $grandchildNodeAggregate) { @@ -282,15 +274,14 @@ private function requireConstraintsImposedByHappyPathStrategyAreMet( * needs to be modified as well (as they are structurally the same) */ private function deleteDisallowedNodesWhenChangingNodeType( + ContentGraphInterface $contentGraph, NodeAggregate $nodeAggregate, - NodeType $newNodeType, - ContentRepository $contentRepository + NodeType $newNodeType ): Events { $events = []; // if we have children, we need to check whether they are still allowed // after we changed the node type of the $nodeAggregate to $newNodeType. - $childNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregates( - $nodeAggregate->contentStreamId, + $childNodeAggregates = $contentGraph->findChildNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($childNodeAggregates as $childNodeAggregate) { @@ -308,9 +299,9 @@ private function deleteDisallowedNodesWhenChangingNodeType( // this aggregate (or parts thereof) are DISALLOWED according to constraints. // We now need to find out which edges we need to remove, $dimensionSpacePointsToBeRemoved = $this->findDimensionSpacePointsConnectingParentAndChildAggregate( + $contentGraph, $nodeAggregate, - $childNodeAggregate, - $contentRepository + $childNodeAggregate ); // AND REMOVE THEM $events[] = $this->removeNodeInDimensionSpacePointSet( @@ -321,13 +312,9 @@ private function deleteDisallowedNodesWhenChangingNodeType( // we do not need to test for grandparents here, as we did not modify the grandparents. // Thus, if it was allowed before, it is allowed now. - // additionally, we need to look one level down to the grandchildren as well // - as it could happen that these are affected by our constraint checks as well. - $grandchildNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregates( - $childNodeAggregate->contentStreamId, - $childNodeAggregate->nodeAggregateId - ); + $grandchildNodeAggregates = $contentGraph->findChildNodeAggregates($childNodeAggregate->nodeAggregateId); foreach ($grandchildNodeAggregates as $grandchildNodeAggregate) { /* @var $grandchildNodeAggregate NodeAggregate */ // we do not need to test for the parent of grandchild (=child), @@ -344,9 +331,9 @@ private function deleteDisallowedNodesWhenChangingNodeType( // this aggregate (or parts thereof) are DISALLOWED according to constraints. // We now need to find out which edges we need to remove, $dimensionSpacePointsToBeRemoved = $this->findDimensionSpacePointsConnectingParentAndChildAggregate( + $contentGraph, $childNodeAggregate, - $grandchildNodeAggregate, - $contentRepository + $grandchildNodeAggregate ); // AND REMOVE THEM $events[] = $this->removeNodeInDimensionSpacePointSet( @@ -361,18 +348,15 @@ private function deleteDisallowedNodesWhenChangingNodeType( } private function deleteObsoleteTetheredNodesWhenChangingNodeType( + ContentGraphInterface $contentGraph, NodeAggregate $nodeAggregate, - NodeType $newNodeType, - ContentRepository $contentRepository + NodeType $newNodeType ): Events { $expectedTetheredNodes = $this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($newNodeType); $events = []; // find disallowed tethered nodes - $tetheredNodeAggregates = $contentRepository->getContentGraph()->findTetheredChildNodeAggregates( - $nodeAggregate->contentStreamId, - $nodeAggregate->nodeAggregateId - ); + $tetheredNodeAggregates = $contentGraph->findTetheredChildNodeAggregates($nodeAggregate->nodeAggregateId); foreach ($tetheredNodeAggregates as $tetheredNodeAggregate) { /* @var $tetheredNodeAggregate NodeAggregate */ @@ -380,9 +364,9 @@ private function deleteObsoleteTetheredNodesWhenChangingNodeType( // this aggregate (or parts thereof) are DISALLOWED according to constraints. // We now need to find out which edges we need to remove, $dimensionSpacePointsToBeRemoved = $this->findDimensionSpacePointsConnectingParentAndChildAggregate( + $contentGraph, $nodeAggregate, - $tetheredNodeAggregate, - $contentRepository + $tetheredNodeAggregate ); // AND REMOVE THEM $events[] = $this->removeNodeInDimensionSpacePointSet( @@ -419,18 +403,15 @@ private function deleteObsoleteTetheredNodesWhenChangingNodeType( * we originated from) */ private function findDimensionSpacePointsConnectingParentAndChildAggregate( + ContentGraphInterface $contentGraph, NodeAggregate $parentNodeAggregate, - NodeAggregate $childNodeAggregate, - ContentRepository $contentRepository + NodeAggregate $childNodeAggregate ): DimensionSpacePointSet { $points = []; foreach ($childNodeAggregate->coveredDimensionSpacePoints as $coveredDimensionSpacePoint) { - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $childNodeAggregate->contentStreamId, - $coveredDimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() + $parentNode = $contentGraph->getSubgraph($coveredDimensionSpacePoint, VisibilityConstraints::withoutRestrictions())->findParentNode( + $childNodeAggregate->nodeAggregateId ); - $parentNode = $subgraph->findParentNode($childNodeAggregate->nodeAggregateId); if ( $parentNode && $parentNode->nodeAggregateId->equals($parentNodeAggregate->nodeAggregateId) diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/Command/CreateNodeVariant.php b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/Command/CreateNodeVariant.php index c842644e845..001f9bd66e9 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/Command/CreateNodeVariant.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/Command/CreateNodeVariant.php @@ -20,7 +20,6 @@ use Neos\ContentRepository\Core\Feature\Common\RebasableToOtherWorkspaceInterface; use Neos\ContentRepository\Core\Feature\WorkspacePublication\Dto\NodeIdToPublishOrDiscard; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php index 7df4a3acb5b..4e14662ae9b 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php @@ -50,12 +50,12 @@ private function handleCreateNodeVariant( CreateNodeVariant $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $this->requireContentStream($command->workspaceName, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); // we do this check first, because it gives a more meaningful error message on what you need to do. // we cannot use sentences with "." because the UI will only print the 1st sentence :/ @@ -66,10 +66,9 @@ private function handleCreateNodeVariant( $this->requireNodeAggregateToOccupyDimensionSpacePoint($nodeAggregate, $command->sourceOrigin); $this->requireNodeAggregateToNotOccupyDimensionSpacePoint($nodeAggregate, $command->targetOrigin); $parentNodeAggregate = $this->requireProjectedParentNodeAggregate( - $contentStreamId, + $contentGraph, $command->nodeAggregateId, - $command->sourceOrigin, - $contentRepository + $command->sourceOrigin ); $this->requireNodeAggregateToCoverDimensionSpacePoint( $parentNodeAggregate, @@ -77,15 +76,14 @@ private function handleCreateNodeVariant( ); $events = $this->createEventsForVariations( - $contentStreamId, + $contentGraph, $command->sourceOrigin, $command->targetOrigin, - $nodeAggregate, - $contentRepository + $nodeAggregate ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId())->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, $events diff --git a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php index 0fb102fe6ef..936529907e6 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php @@ -59,6 +59,7 @@ abstract protected function requireNodeTypeToBeOfTypeRoot(NodeType $nodeType): v /** * @param CreateRootNodeAggregateWithNode $command + * @param ContentRepository $contentRepository * @return EventsToPublish * @throws ContentStreamDoesNotExistYet * @throws NodeAggregateCurrentlyExists @@ -70,20 +71,19 @@ private function handleCreateRootNodeAggregateWithNode( CreateRootNodeAggregateWithNode $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $this->requireContentStream($command->workspaceName, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); $this->requireProjectedNodeAggregateToNotExist( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $nodeType = $this->requireNodeType($command->nodeTypeName); $this->requireNodeTypeToNotBeAbstract($nodeType); $this->requireNodeTypeToBeOfTypeRoot($nodeType); $this->requireRootNodeTypeToBeUnoccupied( - $nodeType->name, - $contentStreamId, - $contentRepository + $contentGraph, + $nodeType->name ); $descendantNodeAggregateIds = $command->tetheredDescendantNodeAggregateIds->completeForNodeOfType( @@ -97,25 +97,24 @@ private function handleCreateRootNodeAggregateWithNode( $events = [ $this->createRootWithNode( $command, - $contentStreamId, + $contentGraph->getContentStreamId(), $this->getAllowedDimensionSubspace() ) ]; foreach ($this->getInterDimensionalVariationGraph()->getRootGeneralizations() as $rootGeneralization) { array_push($events, ...iterator_to_array($this->handleTetheredRootChildNodes( - $contentStreamId, + $contentGraph->getContentStreamId(), $nodeType, OriginDimensionSpacePoint::fromDimensionSpacePoint($rootGeneralization), $this->getInterDimensionalVariationGraph()->getSpecializationSet($rootGeneralization, true), $command->nodeAggregateId, $command->tetheredDescendantNodeAggregateIds, - null, - $contentRepository + null ))); } - $contentStreamEventStream = ContentStreamEventStreamName::fromContentStreamId($contentStreamId); + $contentStreamEventStream = ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()); return new EventsToPublish( $contentStreamEventStream->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( @@ -148,12 +147,11 @@ private function handleUpdateRootNodeAggregateDimensions( UpdateRootNodeAggregateDimensions $command, ContentRepository $contentRepository ): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); if (!$nodeAggregate->classification->isRoot()) { throw new NodeAggregateIsNotRoot('The node aggregate ' . $nodeAggregate->nodeAggregateId->value . ' is not classified as root, but should be for command UpdateRootNodeAggregateDimensions.', 1678647355); @@ -161,14 +159,14 @@ private function handleUpdateRootNodeAggregateDimensions( $events = Events::with( new RootNodeAggregateDimensionsWereUpdated( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $this->getAllowedDimensionSubspace() ) ); $contentStreamEventStream = ContentStreamEventStreamName::fromContentStreamId( - $contentStreamId + $contentGraph->getContentStreamId() ); return new EventsToPublish( $contentStreamEventStream->getEventStreamName(), @@ -191,8 +189,7 @@ private function handleTetheredRootChildNodes( DimensionSpacePointSet $coveredDimensionSpacePoints, NodeAggregateId $parentNodeAggregateId, NodeAggregateIdsByNodePaths $nodeAggregateIdsByNodePath, - ?NodePath $nodePath, - ContentRepository $contentRepository, + ?NodePath $nodePath ): Events { $events = []; foreach ($this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($nodeType) as $rawNodeName => $childNodeType) { @@ -223,8 +220,7 @@ private function handleTetheredRootChildNodes( $coveredDimensionSpacePoints, $childNodeAggregateId, $nodeAggregateIdsByNodePath, - $childNodePath, - $contentRepository + $childNodePath ))); } diff --git a/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php b/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php index 05f3c58248e..64f0ce05b4c 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php +++ b/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php @@ -38,9 +38,9 @@ abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\ private function handleTagSubtree(TagSubtree $command, ContentRepository $contentRepository): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); - $nodeAggregate = $this->requireProjectedNodeAggregate($contentStreamId, $command->nodeAggregateId, $contentRepository); + $nodeAggregate = $this->requireProjectedNodeAggregate($contentGraph, $command->nodeAggregateId); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, $command->coveredDimensionSpacePoint @@ -60,7 +60,7 @@ private function handleTagSubtree(TagSubtree $command, ContentRepository $conten $events = Events::with( new SubtreeWasTagged( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $affectedDimensionSpacePoints, $command->tag, @@ -68,7 +68,7 @@ private function handleTagSubtree(TagSubtree $command, ContentRepository $conten ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId) + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand( $command, @@ -80,12 +80,11 @@ private function handleTagSubtree(TagSubtree $command, ContentRepository $conten public function handleUntagSubtree(UntagSubtree $command, ContentRepository $contentRepository): EventsToPublish { - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( - $contentStreamId, - $command->nodeAggregateId, - $contentRepository + $contentGraph, + $command->nodeAggregateId ); $this->requireNodeAggregateToCoverDimensionSpacePoint( $nodeAggregate, @@ -106,7 +105,7 @@ public function handleUntagSubtree(UntagSubtree $command, ContentRepository $con $events = Events::with( new SubtreeWasUntagged( - $contentStreamId, + $contentGraph->getContentStreamId(), $command->nodeAggregateId, $affectedDimensionSpacePoints, $command->tag, @@ -114,7 +113,7 @@ public function handleUntagSubtree(UntagSubtree $command, ContentRepository $con ); return new EventsToPublish( - ContentStreamEventStreamName::fromContentStreamId($contentStreamId)->getEventStreamName(), + ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId())->getEventStreamName(), NodeAggregateEventPublisher::enrichWithCommand($command, $events), ExpectedVersion::ANY() ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index f03b3a20633..a53f499be11 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -17,6 +17,7 @@ use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\CommandHandler\CommandResult; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\EventStore\DecoratedEvent; use Neos\ContentRepository\Core\EventStore\EventInterface; @@ -24,7 +25,6 @@ use Neos\ContentRepository\Core\EventStore\EventPersister; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; -use Neos\ContentRepository\Core\Feature\Common\ContentStreamIdOverride; use Neos\ContentRepository\Core\Feature\Common\MatchableWithNodeIdToPublishOrDiscardInterface; use Neos\ContentRepository\Core\Feature\Common\PublishableToOtherContentStreamsInterface; use Neos\ContentRepository\Core\Feature\Common\RebasableToOtherWorkspaceInterface; @@ -68,6 +68,7 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Event\WorkspaceWasRebased; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed; use Neos\ContentRepository\Core\Projection\Workspace\Workspace; +use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamAlreadyExists; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; @@ -125,14 +126,19 @@ private function handleCreateWorkspace( CreateWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $existingWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($command->workspaceName); - if ($existingWorkspace !== null) { - throw new WorkspaceAlreadyExists(sprintf( - 'The workspace %s already exists', - $command->workspaceName->value - ), 1505830958921); + try { + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $contentStreamId = $contentGraph->getContentStreamId(); + } catch (ContentStreamDoesNotExistYet $e) { + // Desired outcome } + isset($contentStreamId) + && throw new WorkspaceAlreadyExists(sprintf( + 'The workspace %s already exists', + $command->workspaceName->value + ), 1505830958921); + $baseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($command->baseWorkspaceName); if ($baseWorkspace === null) { throw new BaseWorkspaceDoesNotExist(sprintf( @@ -142,11 +148,12 @@ private function handleCreateWorkspace( ), 1513890708); } + $baseWorkspaceContentGraph = $contentRepository->getContentGraph($command->baseWorkspaceName); // When the workspace is created, we first have to fork the content stream $contentRepository->handle( ForkContentStream::create( $command->newContentStreamId, - $baseWorkspace->currentContentStreamId, + $baseWorkspaceContentGraph->getContentStreamId(), ) )->block(); @@ -171,9 +178,11 @@ private function handleCreateWorkspace( /** * @throws WorkspaceDoesNotExist */ - private function handleRenameWorkspace(RenameWorkspace $command, ContentRepository $contentRepository): EventsToPublish - { - $this->requireWorkspace($command->workspaceName, $contentRepository); + private function handleRenameWorkspace( + RenameWorkspace $command, + ContentRepository $contentRepository + ): EventsToPublish { + $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); $events = Events::with( new WorkspaceWasRenamed( @@ -200,14 +209,19 @@ private function handleCreateRootWorkspace( CreateRootWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $existingWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($command->workspaceName); - if ($existingWorkspace !== null) { - throw new WorkspaceAlreadyExists(sprintf( - 'The workspace %s already exists', - $command->workspaceName->value - ), 1505848624450); + try { + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $contentStreamId = $contentGraph->getContentStreamId(); + } catch (ContentStreamDoesNotExistYet $e) { + // Desired outcome } + isset($contentStreamId) + && throw new WorkspaceAlreadyExists(sprintf( + 'The workspace %s already exists', + $command->workspaceName->value + ), 1505848624450); + $newContentStreamId = $command->newContentStreamId; $contentRepository->handle( CreateContentStream::create( @@ -244,8 +258,8 @@ private function handlePublishWorkspace( PublishWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); $this->publishContentStream( $workspace->currentContentStreamId, @@ -269,6 +283,8 @@ private function handlePublishWorkspace( $workspace->currentContentStreamId, ) ); + + $this->resetContentGraphCache($contentRepository); // if we got so far without an Exception, we can switch the Workspace's active Content stream. return new EventsToPublish( $streamName, @@ -358,8 +374,8 @@ private function handleRebaseWorkspace( RebaseWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; $oldWorkspaceContentStreamIdState = $contentRepository->getContentStreamFinder() ->findStateForContentStream($oldWorkspaceContentStreamId); @@ -389,13 +405,15 @@ private function handleRebaseWorkspace( // 2) extract the commands from the to-be-rebased content stream; and applies them on the new content stream $originalCommands = $this->extractCommandsFromContentStreamMetadata($workspaceContentStreamName); $commandsThatFailed = new CommandsThatFailedDuringRebase(); - ContentStreamIdOverride::applyContentStreamIdToClosure( + $contentRepository->projectionState(ContentGraphFinder::class)->overrideContentStreamId( + $command->workspaceName, $command->rebasedContentStreamId, function () use ($originalCommands, $contentRepository, &$commandsThatFailed): void { foreach ($originalCommands as $sequenceNumber => $originalCommand) { // We no longer need to adjust commands as the workspace stays the same try { $contentRepository->handle($originalCommand)->block(); + // if we came this far, we know the command was applied successfully. } catch (\Exception $e) { $commandsThatFailed = $commandsThatFailed->add( new CommandThatFailedDuringRebase( @@ -419,28 +437,30 @@ function () use ($originalCommands, $contentRepository, &$commandsThatFailed): v ), ); + $this->resetContentGraphCache($contentRepository); return new EventsToPublish( $workspaceStreamName, $events, ExpectedVersion::ANY() ); - } else { - // 3.E) In case of an exception, reopen the old content stream... - $contentRepository->handle( - ReopenContentStream::create( - $oldWorkspaceContentStreamId, - $oldWorkspaceContentStreamIdState, - ) - )->block(); + } - // ... remove the newly created one... - $contentRepository->handle(RemoveContentStream::create( - $rebasedContentStreamId - ))->block(); + // 3.E) In case of an exception, reopen the old content stream... + $contentRepository->handle( + ReopenContentStream::create( + $oldWorkspaceContentStreamId, + $oldWorkspaceContentStreamIdState, + ) + )->block(); - // ...and throw an exception that contains all the information about what exactly failed - throw new WorkspaceRebaseFailed($commandsThatFailed, 'Rebase failed', 1711713880); - } + // ... remove the newly created one... + $contentRepository->handle(RemoveContentStream::create( + $rebasedContentStreamId + ))->block(); + + $this->resetContentGraphCache($contentRepository); + // ...and throw an exception that contains all the information about what exactly failed + throw new WorkspaceRebaseFailed($commandsThatFailed, 'Rebase failed', 1711713880); } /** @@ -490,17 +510,18 @@ private function handlePublishIndividualNodesFromWorkspace( PublishIndividualNodesFromWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; $oldWorkspaceContentStreamIdState = $contentRepository->getContentStreamFinder()->findStateForContentStream($oldWorkspaceContentStreamId); if ($oldWorkspaceContentStreamIdState === null) { throw new \DomainException('Cannot publish nodes on a workspace with a stateless content stream', 1710410114); } - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); // 1) close old content stream $contentRepository->handle( - CloseContentStream::create($oldWorkspaceContentStreamId) + CloseContentStream::create($contentGraph->getContentStreamId()) ); // 2) separate commands in two parts - the ones MATCHING the nodes from the command, and the REST @@ -521,7 +542,8 @@ private function handlePublishIndividualNodesFromWorkspace( try { // 4) using the new content stream, apply the matching commands - ContentStreamIdOverride::applyContentStreamIdToClosure( + $contentRepository->projectionState(ContentGraphFinder::class)->overrideContentStreamId( + $baseWorkspace->workspaceName, $command->contentStreamIdForMatchingPart, function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { foreach ($matchingCommands as $matchingCommand) { @@ -554,7 +576,8 @@ function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { )->block(); // 7) apply REMAINING commands to the workspace's new content stream - ContentStreamIdOverride::applyContentStreamIdToClosure( + $contentRepository->projectionState(ContentGraphFinder::class)->overrideContentStreamId( + $command->workspaceName, $command->contentStreamIdForRemainingPart, function () use ($contentRepository, $remainingCommands) { foreach ($remainingCommands as $remainingCommand) { @@ -583,6 +606,7 @@ function () use ($contentRepository, $remainingCommands) { // in case the exception was thrown before 6), this does not exist } + $this->resetContentGraphCache($contentRepository); throw $exception; } @@ -597,6 +621,7 @@ function () use ($contentRepository, $remainingCommands) { $streamName = WorkspaceEventStreamName::fromWorkspaceName($command->workspaceName)->getEventStreamName(); + $this->resetContentGraphCache($contentRepository); return new EventsToPublish( $streamName, Events::fromArray([ @@ -626,13 +651,14 @@ private function handleDiscardIndividualNodesFromWorkspace( DiscardIndividualNodesFromWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); - $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; - $oldWorkspaceContentStreamIdState = $contentRepository->getContentStreamFinder()->findStateForContentStream($oldWorkspaceContentStreamId); + $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); + $oldWorkspaceContentStreamId = $contentGraph->getContentStreamId(); + $oldWorkspaceContentStreamIdState = $contentRepository->getContentStreamFinder()->findStateForContentStream($contentGraph->getContentStreamId()); if ($oldWorkspaceContentStreamIdState === null) { throw new \DomainException('Cannot discard nodes on a workspace with a stateless content stream', 1710408112); } - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); // 1) close old content stream $contentRepository->handle( @@ -657,7 +683,8 @@ private function handleDiscardIndividualNodesFromWorkspace( // 4) using the new content stream, apply the commands to keep try { - ContentStreamIdOverride::applyContentStreamIdToClosure( + $contentRepository->projectionState(ContentGraphFinder::class)->overrideContentStreamId( + $baseWorkspace->workspaceName, $command->newContentStreamId, function () use ($commandsToKeep, $contentRepository, $baseWorkspace): void { foreach ($commandsToKeep as $matchingCommand) { @@ -687,6 +714,7 @@ function () use ($commandsToKeep, $contentRepository, $baseWorkspace): void { $command->newContentStreamId ))->block(); + $this->resetContentGraphCache($contentRepository); throw $exception; } @@ -697,6 +725,7 @@ function () use ($commandsToKeep, $contentRepository, $baseWorkspace): void { $streamName = WorkspaceEventStreamName::fromWorkspaceName($command->workspaceName)->getEventStreamName(); + $this->resetContentGraphCache($contentRepository); return new EventsToPublish( $streamName, Events::with( @@ -769,8 +798,8 @@ private function handleDiscardWorkspace( DiscardWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); $newContentStream = $command->newContentStreamId; $contentRepository->handle( @@ -811,13 +840,12 @@ private function handleChangeBaseWorkspace( ChangeBaseWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); $this->requireEmptyWorkspace($workspace); - $this->requireBaseWorkspace($workspace, $contentRepository); - - $baseWorkspace = $this->requireWorkspace($command->baseWorkspaceName, $contentRepository); + $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); - $this->requireNonCircularRelationBetweenWorkspaces($workspace, $baseWorkspace, $contentRepository); + $this->requireNonCircularRelationBetweenWorkspaces($workspace, $baseWorkspace, $contentRepository->getWorkspaceFinder()); $contentRepository->handle( ForkContentStream::create( @@ -835,6 +863,7 @@ private function handleChangeBaseWorkspace( ) ); + $this->resetContentGraphCache($contentRepository); return new EventsToPublish( $streamName, $events, @@ -849,7 +878,7 @@ private function handleDeleteWorkspace( DeleteWorkspace $command, ContentRepository $contentRepository, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository); + $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); $contentRepository->handle( RemoveContentStream::create( @@ -876,9 +905,9 @@ private function handleDeleteWorkspace( */ private function handleChangeWorkspaceOwner( ChangeWorkspaceOwner $command, - ContentRepository $contentRepository, + ContentRepository $contentRepository ): EventsToPublish { - $this->requireWorkspace($command->workspaceName, $contentRepository); + $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); $events = Events::with( new WorkspaceOwnerWasChanged( @@ -898,9 +927,9 @@ private function handleChangeWorkspaceOwner( /** * @throws WorkspaceDoesNotExist */ - private function requireWorkspace(WorkspaceName $workspaceName, ContentRepository $contentRepository): Workspace + private function requireWorkspace(WorkspaceName $workspaceName, WorkspaceFinder $workspaceFinder): Workspace { - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName); + $workspace = $workspaceFinder->findOneByName($workspaceName); if (is_null($workspace)) { throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); } @@ -912,14 +941,21 @@ private function requireWorkspace(WorkspaceName $workspaceName, ContentRepositor * @throws WorkspaceHasNoBaseWorkspaceName * @throws BaseWorkspaceDoesNotExist */ - private function requireBaseWorkspace(Workspace $workspace, ContentRepository $contentRepository): Workspace - { + private function requireBaseWorkspace( + Workspace $workspace, + WorkspaceFinder $workspaceFinder + ): Workspace { if (is_null($workspace->baseWorkspaceName)) { throw WorkspaceHasNoBaseWorkspaceName::butWasSupposedTo($workspace->workspaceName); } - $baseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspace->baseWorkspaceName); - if ($baseWorkspace === null) { + try { + $baseWorkspace = $workspaceFinder->findOneByName($workspace->baseWorkspaceName); + } catch (WorkspaceDoesNotExist $_) { + $baseWorkspace = null; + } + + if (is_null($baseWorkspace)) { throw BaseWorkspaceDoesNotExist::butWasSupposedTo($workspace->workspaceName); } @@ -930,18 +966,18 @@ private function requireBaseWorkspace(Workspace $workspace, ContentRepository $c * @throws BaseWorkspaceEqualsWorkspaceException * @throws CircularRelationBetweenWorkspacesException */ - private function requireNonCircularRelationBetweenWorkspaces(Workspace $workspace, Workspace $baseWorkspace, ContentRepository $contentRepository): void + private function requireNonCircularRelationBetweenWorkspaces(Workspace $workspace, Workspace $baseWorkspace, WorkspaceFinder $workspaceFinder): void { if ($workspace->workspaceName->equals($baseWorkspace->workspaceName)) { throw new BaseWorkspaceEqualsWorkspaceException(sprintf('The base workspace of the target must be different from the given workspace "%s".', $workspace->workspaceName->value)); } $nextBaseWorkspace = $baseWorkspace; - while ($nextBaseWorkspace?->baseWorkspaceName !== null) { + while ($nextBaseWorkspace->baseWorkspaceName !== null) { if ($workspace->workspaceName->equals($nextBaseWorkspace->baseWorkspaceName)) { throw new CircularRelationBetweenWorkspacesException(sprintf('The workspace "%s" is already on the path of the target workspace "%s".', $workspace->workspaceName->value, $baseWorkspace->workspaceName->value)); } - $nextBaseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($nextBaseWorkspace->baseWorkspaceName); + $nextBaseWorkspace = $this->requireBaseWorkspace($workspace, $workspaceFinder); } } @@ -978,4 +1014,9 @@ private function hasEventsInContentStreamExceptForking( return false; } + + private function resetContentGraphCache(ContentRepository $contentRepository): void + { + $contentRepository->projectionState(ContentGraphFinder::class)->reset(); + } } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 4d8d7e4a4a7..997d4dcf987 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -24,6 +24,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * This is the MAIN ENTRY POINT for the Content Repository. This class exists only @@ -40,12 +41,12 @@ interface ContentGraphInterface extends ProjectionStateInterface * @api main API method of ContentGraph */ public function getSubgraph( - ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints ): ContentSubgraphInterface; /** + * @throws RootNodeAggregateDoesNotExist * @api * Throws exception if no root aggregate found, because a Content Repository needs at least * one root node to function. @@ -53,10 +54,8 @@ public function getSubgraph( * Also throws exceptions if multiple root node aggregates of the given $nodeTypeName were found, * as this would lead to nondeterministic results in your code. * - * @throws RootNodeAggregateDoesNotExist */ public function findRootNodeAggregateByType( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): NodeAggregate; @@ -64,7 +63,6 @@ public function findRootNodeAggregateByType( * @api */ public function findRootNodeAggregates( - ContentStreamId $contentStreamId, Filter\FindRootNodeAggregatesFilter $filter, ): NodeAggregates; @@ -73,7 +71,6 @@ public function findRootNodeAggregates( * @api */ public function findNodeAggregatesByType( - ContentStreamId $contentStreamId, NodeTypeName $nodeTypeName ): iterable; @@ -82,7 +79,6 @@ public function findNodeAggregatesByType( * @api */ public function findNodeAggregateById( - ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId ): ?NodeAggregate; @@ -98,7 +94,6 @@ public function findUsedNodeTypeNames(): iterable; * @internal only for consumption inside the Command Handler */ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( - ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId, OriginDimensionSpacePoint $childOriginDimensionSpacePoint ): ?NodeAggregate; @@ -108,7 +103,6 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint( * @internal only for consumption inside the Command Handler */ public function findParentNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $childNodeAggregateId ): iterable; @@ -117,7 +111,6 @@ public function findParentNodeAggregates( * @internal only for consumption inside the Command Handler */ public function findChildNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId ): iterable; @@ -129,7 +122,6 @@ public function findChildNodeAggregates( * @internal only for consumption inside the Command Handler */ public function findChildNodeAggregatesByName( - ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId, NodeName $name ): iterable; @@ -139,7 +131,6 @@ public function findChildNodeAggregatesByName( * @internal only for consumption inside the Command Handler */ public function findTetheredChildNodeAggregates( - ContentStreamId $contentStreamId, NodeAggregateId $parentNodeAggregateId ): iterable; @@ -147,7 +138,6 @@ public function findTetheredChildNodeAggregates( * @internal only for consumption inside the Command Handler */ public function getDimensionSpacePointsOccupiedByChildNodeName( - ContentStreamId $contentStreamId, NodeName $nodeName, NodeAggregateId $parentNodeAggregateId, OriginDimensionSpacePoint $parentNodeOriginDimensionSpacePoint, @@ -158,4 +148,8 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( * @internal only for consumption in testcases */ public function countNodes(): int; + + public function getWorkspaceName(): WorkspaceName; + + public function getContentStreamId(): ContentStreamId; } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjection.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjection.php index 018bcb8414d..c5089577235 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjection.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjection.php @@ -4,6 +4,7 @@ namespace Neos\ContentRepository\Core\Projection\ContentGraph; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\EventStore\EventInterface; use Neos\ContentRepository\Core\Projection\CheckpointStorageInterface; use Neos\ContentRepository\Core\Projection\ProjectionInterface; @@ -12,13 +13,13 @@ use Neos\EventStore\Model\EventEnvelope; /** - * @implements ProjectionInterface + * @implements ProjectionInterface * @api people load this projection class name to access the Content Graph */ final class ContentGraphProjection implements ProjectionInterface, WithMarkStaleInterface { /** - * @param WithMarkStaleInterface&ProjectionInterface $projectionImplementation + * @param WithMarkStaleInterface&ProjectionInterface $projectionImplementation */ public function __construct( private readonly ProjectionInterface&WithMarkStaleInterface $projectionImplementation @@ -45,7 +46,7 @@ public function canHandle(EventInterface $event): bool return $this->projectionImplementation->canHandle($event); } - public function getState(): ContentGraphInterface + public function getState(): ContentGraphFinder { return $this->projectionImplementation->getState(); } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php index 0d1f7d3f8f5..79a1427ec93 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php @@ -33,7 +33,7 @@ * From the central Content Repository instance, you can fetch the singleton * {@see ContentGraphInterface}. There, you can call * {@see ContentGraphInterface::getSubgraph()} and pass in - * the {@see ContentStreamId}, {@see DimensionSpacePoint} and + * the {@see DimensionSpacePoint} and * {@see VisibilityConstraints} you want to have. * * diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php b/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php index 6bcc6a9b2b0..c159398fd06 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php @@ -179,21 +179,4 @@ public function findVersionForContentStream(ContentStreamId $contentStreamId): M return MaybeVersion::fromVersionOrNull(Version::fromInteger($version)); } - - public function hasContentStream(ContentStreamId $contentStreamId): bool - { - $connection = $this->client->getConnection(); - /* @var $state string|false */ - $version = $connection->executeQuery( - ' - SELECT version FROM ' . $this->tableName . ' - WHERE contentStreamId = :contentStreamId - ', - [ - 'contentStreamId' => $contentStreamId->value - ] - )->fetchOne(); - - return $version !== false; - } } diff --git a/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php b/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php index 0b65f505f1c..5a138b0df7e 100644 --- a/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php +++ b/Neos.ContentRepository.Core/Classes/Service/ContentRepositoryBootstrapper.php @@ -70,9 +70,7 @@ public function getOrCreateRootNodeAggregate( NodeTypeName $rootNodeTypeName ): NodeAggregateId { try { - $contentStreamId = $workspace->currentContentStreamId; - return $this->contentRepository->getContentGraph()->findRootNodeAggregateByType( - $contentStreamId, + return $this->contentRepository->getContentGraph($workspace->workspaceName)->findRootNodeAggregateByType( $rootNodeTypeName )->nodeAggregateId; diff --git a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php index ff9511a1011..dd5302406c3 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Tests/Behavior/Bootstrap/FeatureContext.php @@ -18,6 +18,7 @@ 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\ContentRepository\Export\Asset\AssetExporter; use Neos\ContentRepository\Export\Asset\AssetLoaderInterface; use Neos\ContentRepository\Export\Asset\ResourceLoaderInterface; @@ -123,10 +124,14 @@ public function iRunTheEventMigration(string $contentStream = null): void { $nodeTypeManager = $this->currentContentRepository->getNodeTypeManager(); $propertyMapper = $this->getObject(PropertyMapper::class); - $contentGraph = $this->currentContentRepository->getContentGraph(); - $nodeFactory = (new \ReflectionClass($contentGraph)) + $contentGraphFinder = $this->currentContentRepository->projectionState(\Neos\ContentRepository\Core\ContentGraphFinder::class); + // FIXME: Dirty + $contentGraphFactory = (new \ReflectionClass($contentGraphFinder)) + ->getProperty('contentGraphFactory') + ->getValue($contentGraphFinder); + $nodeFactory = (new \ReflectionClass($contentGraphFactory)) ->getProperty('nodeFactory') - ->getValue($contentGraph); + ->getValue($contentGraphFactory); $propertyConverter = (new \ReflectionClass($nodeFactory)) ->getProperty('propertyConverter') ->getValue($nodeFactory); diff --git a/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php b/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php index 8bd7c065860..23b18fc4bee 100644 --- a/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php +++ b/Neos.ContentRepository.NodeMigration/src/NodeMigrationService.php @@ -130,10 +130,10 @@ protected function executeSubMigrationAndBlock( if ($transformations->containsGlobal()) { $transformations->executeGlobalAndBlock($workspaceNameForWriting); } elseif ($transformations->containsNodeAggregateBased()) { - foreach ($this->contentRepository->getContentGraph()->findUsedNodeTypeNames() as $nodeTypeName) { + $contentGraph = $this->contentRepository->getContentGraph($workspaceForReading->workspaceName); + foreach ($contentGraph->findUsedNodeTypeNames() as $nodeTypeName) { foreach ( - $this->contentRepository->getContentGraph()->findNodeAggregatesByType( - $workspaceForReading->currentContentStreamId, + $contentGraph->findNodeAggregatesByType( $nodeTypeName ) as $nodeAggregate ) { @@ -143,10 +143,10 @@ protected function executeSubMigrationAndBlock( } } } elseif ($transformations->containsNodeBased()) { - foreach ($this->contentRepository->getContentGraph()->findUsedNodeTypeNames() as $nodeTypeName) { + $contentGraph = $this->contentRepository->getContentGraph($workspaceForReading->workspaceName); + foreach ($contentGraph->findUsedNodeTypeNames() as $nodeTypeName) { foreach ( - $this->contentRepository->getContentGraph()->findNodeAggregatesByType( - $workspaceForReading->currentContentStreamId, + $contentGraph->findNodeAggregatesByType( $nodeTypeName ) as $nodeAggregate ) { diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php index c715e22748c..5f602315ddd 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php @@ -24,7 +24,6 @@ class DisallowedChildNodeAdjustment use RemoveNodeAggregateTrait; public function __construct( - private readonly ContentRepository $contentRepository, private readonly ProjectedNodeIterator $projectedNodeIterator, private readonly NodeTypeManager $nodeTypeManager, ) { @@ -51,8 +50,7 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat // as it can happen that the constraint is only violated in e.g. "AT", but not in "DE". // Then, we only want to remove the single edge. foreach ($nodeAggregate->coveredDimensionSpacePoints as $coveredDimensionSpacePoint) { - $subgraph = $this->contentRepository->getContentGraph()->getSubgraph( - $nodeAggregate->contentStreamId, + $subgraph = $this->projectedNodeIterator->contentGraph->getSubgraph( $coveredDimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); @@ -68,7 +66,7 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat $parentNodeType = $this->nodeTypeManager->getNodeType($parentNode->nodeTypeName); if ($parentNodeType) { $allowedByParent = $parentNodeType->allowsChildNodeType($nodeType) - || $nodeAggregate->nodeName && $parentNodeType->hasTetheredNode($nodeAggregate->nodeName); + || ($nodeAggregate->nodeName && $parentNodeType->hasTetheredNode($nodeAggregate->nodeName)); } } diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/ProjectedNodeIterator.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/ProjectedNodeIterator.php index e7548998909..69e50487315 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/ProjectedNodeIterator.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/ProjectedNodeIterator.php @@ -8,8 +8,6 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * Low-Level helper service, iterating over the "real" Nodes in the Live workspace; that is, the nodes, @@ -22,13 +20,10 @@ */ class ProjectedNodeIterator { - protected WorkspaceFinder $workspaceFinder; - protected ContentGraphInterface $contentGraph; - - public function __construct(WorkspaceFinder $workspaceFinder, ContentGraphInterface $contentGraph) + public function __construct( + public readonly ContentGraphInterface $contentGraph + ) { - $this->workspaceFinder = $workspaceFinder; - $this->contentGraph = $contentGraph; } /** @@ -37,18 +32,9 @@ public function __construct(WorkspaceFinder $workspaceFinder, ContentGraphInterf */ public function nodeAggregatesOfType(NodeTypeName $nodeTypeName): iterable { - $contentStreamId = $this->findLiveContentStream(); - $nodeAggregates = $this->contentGraph->findNodeAggregatesByType($contentStreamId, $nodeTypeName); + $nodeAggregates = $this->contentGraph->findNodeAggregatesByType($nodeTypeName); foreach ($nodeAggregates as $nodeAggregate) { yield $nodeAggregate; } } - - private function findLiveContentStream(): ContentStreamId - { - $liveWorkspace = $this->workspaceFinder->findOneByName(WorkspaceName::forLive()); - assert($liveWorkspace !== null, 'Live workspace not found'); - - return $liveWorkspace->currentContentStreamId; - } } diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index d0f0479a9ba..469da6ec48d 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -18,11 +18,13 @@ use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\EventStore\Model\EventStream\ExpectedVersion; class TetheredNodeAdjustments @@ -53,6 +55,8 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat $expectedTetheredNodes = $this->nodeTypeManager->getTetheredNodesConfigurationForNodeType($nodeType); foreach ($this->projectedNodeIterator->nodeAggregatesOfType($nodeTypeName) as $nodeAggregate) { + // TODO: We should use $nodeAggregate->workspaceName as soon as it's available + $contentGraph = $this->contentRepository->getContentGraph(WorkspaceName::forLive()); // find missing tethered nodes $foundMissingOrDisallowedTetheredNodes = false; $originDimensionSpacePoints = $nodeType->isOfType(NodeTypeName::ROOT_NODE_TYPE_NAME) @@ -65,14 +69,12 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat foreach ($expectedTetheredNodes as $tetheredNodeName => $expectedTetheredNodeType) { $tetheredNodeName = NodeName::fromString($tetheredNodeName); - $subgraph = $this->contentRepository->getContentGraph()->getSubgraph( - $nodeAggregate->contentStreamId, + $tetheredNode = $contentGraph->getSubgraph( $originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() - ); - $tetheredNode = $subgraph->findNodeByPath( + )->findNodeByPath( $tetheredNodeName, - $nodeAggregate->nodeAggregateId, + $nodeAggregate->nodeAggregateId ); if ($tetheredNode === null) { $foundMissingOrDisallowedTetheredNodes = true; @@ -84,14 +86,14 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat $nodeAggregate->nodeAggregateId, StructureAdjustment::TETHERED_NODE_MISSING, 'The tethered child node "' . $tetheredNodeName->value . '" is missing.', - function () use ($nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, $expectedTetheredNodeType) { + function () use ($nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, $expectedTetheredNodeType, $contentGraph) { $events = $this->createEventsForMissingTetheredNode( + $contentGraph, $nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, null, - $expectedTetheredNodeType, - $this->contentRepository + $expectedTetheredNodeType ); $streamName = ContentStreamEventStreamName::fromContentStreamId($nodeAggregate->contentStreamId); @@ -110,8 +112,7 @@ function () use ($nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, } // find disallowed tethered nodes - $tetheredNodeAggregates = $this->contentRepository->getContentGraph()->findTetheredChildNodeAggregates( - $nodeAggregate->contentStreamId, + $tetheredNodeAggregates = $contentGraph->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($tetheredNodeAggregates as $tetheredNodeAggregate) { @@ -133,12 +134,7 @@ function () use ($tetheredNodeAggregate) { // find wrongly ordered tethered nodes if ($foundMissingOrDisallowedTetheredNodes === false) { foreach ($originDimensionSpacePoints as $originDimensionSpacePoint) { - $subgraph = $this->contentRepository->getContentGraph()->getSubgraph( - $nodeAggregate->contentStreamId, - $originDimensionSpacePoint->toDimensionSpacePoint(), - VisibilityConstraints::withoutRestrictions() - ); - $childNodes = $subgraph->findChildNodes($nodeAggregate->nodeAggregateId, FindChildNodesFilter::create()); + $childNodes = $contentGraph->getSubgraph($originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions())->findChildNodes($nodeAggregate->nodeAggregateId, FindChildNodesFilter::create()); /** is indexed by node name, and the value is the tethered node itself */ $actualTetheredChildNodes = []; @@ -258,4 +254,10 @@ private function reorderNodes( ExpectedVersion::ANY() ); } + + protected function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface + { + return $this->contentRepository->getContentGraph($workspaceName); + } + } diff --git a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php index 3f50d52afe7..f1b0222d607 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php +++ b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php @@ -12,6 +12,7 @@ use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepository\StructureAdjustment\Adjustment\DimensionAdjustment; use Neos\ContentRepository\StructureAdjustment\Adjustment\DisallowedChildNodeAdjustment; use Neos\ContentRepository\StructureAdjustment\Adjustment\ProjectedNodeIterator; @@ -33,11 +34,10 @@ public function __construct( private readonly EventPersister $eventPersister, NodeTypeManager $nodeTypeManager, InterDimensionalVariationGraph $interDimensionalVariationGraph, - PropertyConverter $propertyConverter + PropertyConverter $propertyConverter, ) { $projectedNodeIterator = new ProjectedNodeIterator( - $contentRepository->getWorkspaceFinder(), - $contentRepository->getContentGraph(), + $contentRepository->getContentGraph(WorkspaceName::forLive()), ); $this->tetheredNodeAdjustments = new TetheredNodeAdjustments( @@ -53,7 +53,6 @@ public function __construct( $nodeTypeManager ); $this->disallowedChildNodeAdjustment = new DisallowedChildNodeAdjustment( - $this->contentRepository, $projectedNodeIterator, $nodeTypeManager ); @@ -73,7 +72,7 @@ public function __construct( */ public function findAllAdjustments(): \Generator { - foreach ($this->contentRepository->getContentGraph()->findUsedNodeTypeNames() as $nodeTypeName) { + foreach ($this->contentRepository->getContentGraph(WorkspaceName::forLive())->findUsedNodeTypeNames() as $nodeTypeName) { yield from $this->findAdjustmentsForNodeType($nodeTypeName); } } diff --git a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentServiceFactory.php b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentServiceFactory.php index 3363a2ec7fb..b9e75abeaff 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentServiceFactory.php +++ b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentServiceFactory.php @@ -19,7 +19,7 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor $serviceFactoryDependencies->eventPersister, $serviceFactoryDependencies->nodeTypeManager, $serviceFactoryDependencies->interDimensionalVariationGraph, - $serviceFactoryDependencies->propertyConverter + $serviceFactoryDependencies->propertyConverter, ); } } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php index a19eb219002..89f9f15cf67 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php @@ -15,6 +15,7 @@ namespace Neos\ContentRepository\TestSuite\Behavior\Features\Bootstrap; use Neos\ContentRepository\Core\CommandHandler\CommandResult; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; @@ -160,8 +161,13 @@ public function visibilityConstraintsAreSetTo(string $restrictionType): void public function getCurrentSubgraph(): ContentSubgraphInterface { - return $this->currentContentRepository->getContentGraph()->getSubgraph( - $this->currentContentStreamId, + $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); + $contentGraphFinder->reset(); + if (isset($this->currentContentStreamId)) { + return $contentGraphFinder->fromWorkspaceNameAndContentStreamId($this->currentWorkspaceName, $this->currentContentStreamId)->getSubgraph($this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); + } + + return $contentGraphFinder->fromWorkspaceName($this->currentWorkspaceName)->getSubgraph( $this->currentDimensionSpacePoint, $this->currentVisibilityConstraints ); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php index 832cfcef5fa..11a35b4acac 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php @@ -195,7 +195,7 @@ public function workspaceHasStatus(string $rawWorkspaceName, string $status): vo */ public function iExpectTheGraphProjectionToConsistOfExactlyNodes(int $expectedNumberOfNodes): void { - $actualNumberOfNodes = $this->currentContentRepository->getContentGraph()->countNodes(); + $actualNumberOfNodes = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->countNodes(); Assert::assertSame($expectedNumberOfNodes, $actualNumberOfNodes, 'Content graph consists of ' . $actualNumberOfNodes . ' nodes, expected were ' . $expectedNumberOfNodes . '.'); } @@ -262,8 +262,7 @@ protected function getRootNodeAggregateId(): ?NodeAggregateId } try { - return $this->currentContentRepository->getContentGraph()->findRootNodeAggregateByType( - $this->currentContentStreamId, + return $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->findRootNodeAggregateByType( NodeTypeName::fromString('Neos.Neos:Sites') )->nodeAggregateId; } catch (RootNodeAggregateDoesNotExist) { diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCopying.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCopying.php index f9c85562631..ae19812dc32 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCopying.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCopying.php @@ -39,8 +39,7 @@ abstract protected function readPayloadTable(TableNode $payloadTable): array; public function theCommandCopyNodesRecursivelyIsExecutedCopyingTheCurrentNodeAggregateWithPayload(TableNode $payloadTable): void { $commandArguments = $this->readPayloadTable($payloadTable); - $subgraph = $this->currentContentRepository->getContentGraph()->getSubgraph( - $this->currentContentStreamId, + $subgraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->getSubgraph( $this->currentDimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeAggregateTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeAggregateTrait.php index b72d26e82c4..38ec865c887 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeAggregateTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeAggregateTrait.php @@ -42,7 +42,7 @@ public function iExpectTheNodeAggregateToExist(string $serializedNodeAggregateId { $nodeAggregateId = NodeAggregateId::fromString($serializedNodeAggregateId); $this->initializeCurrentNodeAggregate(function (ContentGraphInterface $contentGraph) use ($nodeAggregateId) { - $currentNodeAggregate = $contentGraph->findNodeAggregateById($this->currentContentStreamId, $nodeAggregateId); + $currentNodeAggregate = $contentGraph->findNodeAggregateById($nodeAggregateId); Assert::assertNotNull($currentNodeAggregate, sprintf('Node aggregate "%s" was not found in the current content stream "%s".', $nodeAggregateId->value, $this->currentContentStreamId->value)); return $currentNodeAggregate; }); @@ -50,7 +50,7 @@ public function iExpectTheNodeAggregateToExist(string $serializedNodeAggregateId protected function initializeCurrentNodeAggregate(callable $query): void { - $this->currentNodeAggregate = $query($this->currentContentRepository->getContentGraph()); + $this->currentNodeAggregate = $query($this->currentContentRepository->getContentGraph($this->currentWorkspaceName)); } /** @@ -159,8 +159,7 @@ public function iExpectThisNodeAggregateToHaveNoParentNodeAggregates(): void { $this->assertOnCurrentNodeAggregate(function (NodeAggregate $nodeAggregate) { Assert::assertEmpty( - iterator_to_array($this->currentContentRepository->getContentGraph()->findParentNodeAggregates( - $nodeAggregate->contentStreamId, + iterator_to_array($this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->findParentNodeAggregates( $nodeAggregate->nodeAggregateId )), 'Did not expect parent node aggregates.' @@ -182,8 +181,7 @@ public function iExpectThisNodeAggregateToHaveTheParentNodeAggregates(string $se fn (NodeAggregate $parentNodeAggregate): string => $parentNodeAggregate->contentStreamId->value . ';' . $parentNodeAggregate->nodeAggregateId->value, iterator_to_array( - $this->currentContentRepository->getContentGraph()->findParentNodeAggregates( - $nodeAggregate->contentStreamId, + $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->findParentNodeAggregates( $nodeAggregate->nodeAggregateId ) ) @@ -203,8 +201,7 @@ public function iExpectThisNodeAggregateToHaveNoChildNodeAggregates(): void { $this->assertOnCurrentNodeAggregate(function (NodeAggregate $nodeAggregate) { Assert::assertEmpty( - iterator_to_array($this->currentContentRepository->getContentGraph()->findChildNodeAggregates( - $nodeAggregate->contentStreamId, + iterator_to_array($this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->findChildNodeAggregates( $nodeAggregate->nodeAggregateId )), 'No child node aggregates were expected.' @@ -227,8 +224,7 @@ public function iExpectThisNodeAggregateToHaveTheChildNodeAggregates(string $ser $actualDiscriminators = array_values(array_map( fn (NodeAggregate $parentNodeAggregate): string => $parentNodeAggregate->contentStreamId->value . ':' . $parentNodeAggregate->nodeAggregateId->value, - iterator_to_array($this->currentContentRepository->getContentGraph()->findChildNodeAggregates( - $nodeAggregate->contentStreamId, + iterator_to_array($this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->findChildNodeAggregates( $nodeAggregate->nodeAggregateId )) )); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index bee6d1ffec9..7d6d00f32dc 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -16,6 +16,7 @@ use Behat\Gherkin\Node\TableNode; use GuzzleHttp\Psr7\Uri; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; @@ -80,9 +81,9 @@ public function iGetTheNodeAtPath(string $serializedNodePath): void public function iExpectANodeIdentifiedByXToExistInTheContentGraph(string $serializedNodeDiscriminator): void { $nodeDiscriminator = NodeDiscriminator::fromShorthand($serializedNodeDiscriminator); + $this->currentContentStreamId = $nodeDiscriminator->contentStreamId; $this->initializeCurrentNodeFromContentGraph(function (ContentGraphInterface $contentGraph) use ($nodeDiscriminator) { $currentNodeAggregate = $contentGraph->findNodeAggregateById( - $nodeDiscriminator->contentStreamId, $nodeDiscriminator->nodeAggregateId ); Assert::assertTrue( @@ -265,7 +266,15 @@ public function iExpectThisNodeToExactlyInheritTheTags(string $tagList): void protected function initializeCurrentNodeFromContentGraph(callable $query): void { - $this->currentNode = $query($this->currentContentRepository->getContentGraph()); + $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); + $contentGraphFinder->reset(); + if (isset($this->currentContentStreamId)) { + $contentGraph = $contentGraphFinder->fromWorkspaceNameAndContentStreamId($this->currentWorkspaceName, $this->currentContentStreamId); + } else { + $contentGraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName); + } + // FIXME: query->workspaceName this _might_ be wrong as the query could use a different workspace/contentstream + $this->currentNode = $query($contentGraph); } protected function initializeCurrentNodeFromContentSubgraph(callable $query): void diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/ContentCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/ContentCommandController.php index d4b5c15dc61..87e8e0e936d 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Command/ContentCommandController.php +++ b/Neos.ContentRepositoryRegistry/Classes/Command/ContentCommandController.php @@ -60,8 +60,7 @@ public function refreshRootNodeDimensionsCommand(string $contentRepository = 'de $this->outputLine('Refreshing root node dimensions in workspace %s (content repository %s)', [$workspaceInstance->workspaceName->value, $contentRepositoryId->value]); $this->outputLine('Resolved content stream %s', [$workspaceInstance->currentContentStreamId->value]); - $rootNodeAggregates = $contentRepositoryInstance->getContentGraph()->findRootNodeAggregates( - $workspaceInstance->currentContentStreamId, + $rootNodeAggregates = $contentRepositoryInstance->getContentGraph($workspaceInstance->workspaceName)->findRootNodeAggregates( FindRootNodeAggregatesFilter::create() ); @@ -153,14 +152,13 @@ public function createVariantsRecursivelyCommand(string $source, string $target, $this->outputLine('Creating %s to %s in workspace %s (content repository %s)', [$sourceSpacePoint->toJson(), $targetSpacePoint->toJson(), $workspaceInstance->workspaceName->value, $contentRepositoryId->value]); $this->outputLine('Resolved content stream %s', [$workspaceInstance->currentContentStreamId->value]); - $sourceSubgraph = $contentRepositoryInstance->getContentGraph()->getSubgraph( - $workspaceInstance->currentContentStreamId, + $sourceSubgraph = $contentRepositoryInstance->getContentGraph(WorkspaceName::fromString($workspace))->getSubgraph( $sourceSpacePoint, VisibilityConstraints::withoutRestrictions() ); - $rootNodeAggregates = $contentRepositoryInstance->getContentGraph() - ->findRootNodeAggregates($workspaceInstance->currentContentStreamId, FindRootNodeAggregatesFilter::create()); + $rootNodeAggregates = $contentRepositoryInstance->getContentGraph($workspaceInstance->workspaceName) + ->findRootNodeAggregates(FindRootNodeAggregatesFilter::create()); foreach ($rootNodeAggregates as $rootNodeAggregate) { diff --git a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php index 25daca7cf84..a35269902f7 100644 --- a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php +++ b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php @@ -15,6 +15,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ProjectionCatchUpTriggerInterface; use Neos\ContentRepository\Core\Projection\ProjectionFactoryInterface; +use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\User\UserIdProviderInterface; use Neos\ContentRepositoryRegistry\Exception\ContentRepositoryNotFoundException; @@ -93,8 +94,12 @@ public function resetFactoryInstance(ContentRepositoryId $contentRepositoryId): public function subgraphForNode(Node $node): ContentSubgraphInterface { $contentRepository = $this->get($node->subgraphIdentity->contentRepositoryId); - return $contentRepository->getContentGraph()->getSubgraph( - $node->subgraphIdentity->contentStreamId, + + // FIXME: node->workspace + /** @var Workspace $workspace */ + $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($node->subgraphIdentity->contentStreamId); + + return $contentRepository->getContentGraph($workspace->workspaceName)->getSubgraph( $node->subgraphIdentity->dimensionSpacePoint, $node->subgraphIdentity->visibilityConstraints ); @@ -157,7 +162,7 @@ private function buildFactory(ContentRepositoryId $contentRepositoryId): Content $this->buildProjectionsFactory($contentRepositoryId, $contentRepositorySettings), $this->buildProjectionCatchUpTrigger($contentRepositoryId, $contentRepositorySettings), $this->buildUserIdProvider($contentRepositoryId, $contentRepositorySettings), - $clock, + $clock ); } catch (\Exception $exception) { throw InvalidConfigurationException::fromException($contentRepositoryId, $exception); diff --git a/Neos.Media.Browser/Classes/Controller/UsageController.php b/Neos.Media.Browser/Classes/Controller/UsageController.php index e53cfa0df5a..2fd3b3f8ef8 100644 --- a/Neos.Media.Browser/Classes/Controller/UsageController.php +++ b/Neos.Media.Browser/Classes/Controller/UsageController.php @@ -98,7 +98,10 @@ public function relatedNodesAction(AssetInterface $asset) $contentRepository = $this->contentRepositoryRegistry->get($usage->getContentRepositoryId()); - $nodeAggregate = $contentRepository->getContentGraph()->findNodeAggregateById( + $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->getContentStreamId()); + + // FIXME: AssetUsageReference->workspaceName ? + $nodeAggregate = $contentRepository->getContentGraph($workspace->workspaceName)->findNodeAggregateById( $usage->getContentStreamId(), $usage->getNodeAggregateId() ); @@ -107,7 +110,7 @@ public function relatedNodesAction(AssetInterface $asset) } catch (NodeTypeNotFoundException $e) { $nodeType = null; } - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->getContentStreamId()); + $accessible = $this->domainUserService->currentUserCanReadWorkspace($workspace); $inaccessibleRelation['nodeIdentifier'] = $usage->getNodeAggregateId()->value; @@ -121,7 +124,7 @@ public function relatedNodesAction(AssetInterface $asset) continue; } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( + $subgraph = $contentRepository->getContentGraph($workspace->workspaceName)->getSubgraph( $usage->getContentStreamId(), $usage->getOriginDimensionSpacePoint()->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() diff --git a/Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php b/Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php index d373d57f9cc..cfb347b0a6d 100644 --- a/Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php +++ b/Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php @@ -9,6 +9,7 @@ /** * @internal */ + #[Flow\Proxy(false)] readonly class AssetIdAndOriginalAssetId { diff --git a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php index 02a77386db9..e00e9b33014 100644 --- a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php +++ b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php @@ -4,9 +4,11 @@ namespace Neos\Neos\AssetUsage\Service; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Repository\AssetRepository; use Neos\Neos\AssetUsage\Dto\AssetUsage; @@ -27,7 +29,8 @@ class AssetUsageSyncService implements ContentRepositoryServiceInterface public function __construct( private readonly AssetUsageFinder $assetUsageFinder, - private readonly ContentGraphInterface $contentGraph, + private readonly ContentGraphFinder $contentGraphFinder, + private readonly WorkspaceFinder $workspaceFinder, private readonly AssetRepository $assetRepository, private readonly AssetUsageRepository $assetUsageRepository, ) { @@ -55,8 +58,12 @@ public function isAssetUsageStillValid(AssetUsage $usage): bool } $dimensionSpacePoint = $usage->originDimensionSpacePoint->toDimensionSpacePoint(); - $subGraph = $this->contentGraph->getSubgraph( - $usage->contentStreamId, + // FIXME: AssetUsage->workspaceName ? + $workspace = $this->workspaceFinder->findOneByCurrentContentStreamId($usage->contentStreamId); + if (is_null($workspace)) { + return false; + } + $subGraph = $this->contentGraphFinder->fromWorkspaceName($workspace->workspaceName) ->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncServiceFactory.php b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncServiceFactory.php index 524ea542c12..67339cd88ba 100644 --- a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncServiceFactory.php +++ b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncServiceFactory.php @@ -4,6 +4,7 @@ namespace Neos\Neos\AssetUsage\Service; +use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\Media\Domain\Repository\AssetRepository; @@ -27,7 +28,8 @@ public function build( ): AssetUsageSyncService { return new AssetUsageSyncService( $serviceFactoryDependencies->contentRepository->projectionState(AssetUsageFinder::class), - $serviceFactoryDependencies->contentRepository->getContentGraph(), + $serviceFactoryDependencies->contentRepository->projectionState(ContentGraphFinder::class), + $serviceFactoryDependencies->contentRepository->getWorkspaceFinder(), $this->assetRepository, $this->assetUsageRepositoryFactory->build($serviceFactoryDependencies->contentRepositoryId), ); diff --git a/Neos.Neos/Classes/Controller/Backend/ContentController.php b/Neos.Neos/Classes/Controller/Backend/ContentController.php index 707cca4d8e9..dd474086c97 100644 --- a/Neos.Neos/Classes/Controller/Backend/ContentController.php +++ b/Neos.Neos/Classes/Controller/Backend/ContentController.php @@ -152,9 +152,8 @@ public function uploadAssetAction(Asset $asset, string $metadata, string $node, $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); $nodeAddress = NodeAddressFactory::create($contentRepository)->createFromUriString($nodeAddressString); - $node = $contentRepository->getContentGraph() + $node = $contentRepository->getContentGraph($nodeAddress->workspaceName) ->getSubgraph( - $nodeAddress->contentStreamId, $nodeAddress->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ) diff --git a/Neos.Neos/Classes/Controller/Frontend/NodeController.php b/Neos.Neos/Classes/Controller/Frontend/NodeController.php index 4bc115447ee..6f3a607f830 100644 --- a/Neos.Neos/Classes/Controller/Frontend/NodeController.php +++ b/Neos.Neos/Classes/Controller/Frontend/NodeController.php @@ -132,8 +132,7 @@ public function previewAction(string $node): void $nodeAddress = NodeAddressFactory::create($contentRepository)->createFromUriString($node); - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $nodeAddress->contentStreamId, + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, $visibilityConstraints ); @@ -201,8 +200,7 @@ public function showAction(string $node): void throw new NodeNotFoundException('The requested node isn\'t accessible to the current user', 1430218623); } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $nodeAddress->contentStreamId, + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, VisibilityConstraints::frontend() ); diff --git a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php index 24dd502fb69..5dca97ed900 100755 --- a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php +++ b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php @@ -185,8 +185,7 @@ public function updateSiteAction(Site $site, $newSiteNodeName) } try { - $sitesNode = $contentRepository->getContentGraph()->findRootNodeAggregateByType( - $liveWorkspace->currentContentStreamId, + $sitesNode = $contentRepository->getContentGraph($liveWorkspace->workspaceName)->findRootNodeAggregateByType( NodeTypeNameFactory::forSites() ); } catch (\Exception $exception) { @@ -208,8 +207,7 @@ public function updateSiteAction(Site $site, $newSiteNodeName) // technically, due to the name being the "identifier", there might be more than one :/ /** @var NodeAggregate[] $siteNodeAggregates */ /** @var Workspace $workspace */ - $siteNodeAggregates = $contentRepository->getContentGraph()->findChildNodeAggregatesByName( - $workspace->currentContentStreamId, + $siteNodeAggregates = $contentRepository->getContentGraph($workspace->workspaceName)->findChildNodeAggregatesByName( $sitesNode->nodeAggregateId, $site->getNodeName()->toNodeName() ); diff --git a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php index 4db51ab429e..c603cd82dcf 100644 --- a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php +++ b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php @@ -457,8 +457,8 @@ public function rebaseAndRedirectAction(Node $targetNode, Workspace $targetWorks throw new \RuntimeException('No account is authenticated', 1710068880); } $personalWorkspaceName = WorkspaceNameBuilder::fromAccountIdentifier($currentAccount->getAccountIdentifier()); - $personalWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($personalWorkspaceName); /** @var Workspace $personalWorkspace */ + $personalWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($personalWorkspaceName); /** @todo do something else * if ($personalWorkspace !== $targetWorkspace) { @@ -750,8 +750,7 @@ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepos ); foreach ($changes as $change) { - $contentStreamId = $change->contentStreamId; - + $workspaceName = $selectedWorkspace->workspaceName; if ($change->deleted) { // If we deleted a node, there is no way for us to anymore find the deleted node in the ContentStream // where the node was deleted. @@ -759,10 +758,9 @@ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepos // // This is safe because the UI basically shows what would be removed once the deletion is published. $baseWorkspace = $this->getBaseWorkspaceWhenSureItExists($selectedWorkspace, $contentRepository); - $contentStreamId = $baseWorkspace->currentContentStreamId; + $workspaceName = $baseWorkspace->workspaceName; } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $subgraph = $contentRepository->getContentGraph($workspaceName)->getSubgraph( $change->originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() ); @@ -872,17 +870,14 @@ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepos */ protected function getOriginalNode( Node $modifiedNode, - ContentStreamId $baseContentStreamId, + WorkspaceName $workspaceName, ContentRepository $contentRepository, ): ?Node { - $baseSubgraph = $contentRepository->getContentGraph()->getSubgraph( - $baseContentStreamId, + $baseSubgraph = $contentRepository->getContentGraph($workspaceName)->getSubgraph( $modifiedNode->subgraphIdentity->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); - $node = $baseSubgraph->findNodeById($modifiedNode->nodeAggregateId); - - return $node; + return $baseSubgraph->findNodeById($modifiedNode->nodeAggregateId); } /** @@ -902,8 +897,7 @@ protected function renderContentChanges( $originalNode = null; if ($currentWorkspace !== null) { $baseWorkspace = $this->getBaseWorkspaceWhenSureItExists($currentWorkspace, $contentRepository); - $baseContentStreamId = $baseWorkspace->currentContentStreamId; - $originalNode = $this->getOriginalNode($changedNode, $baseContentStreamId, $contentRepository); + $originalNode = $this->getOriginalNode($changedNode, $baseWorkspace->workspaceName, $contentRepository); } @@ -914,7 +908,7 @@ protected function renderContentChanges( $renderer = new HtmlArrayRenderer(); foreach ($changedNode->properties as $propertyName => $changedPropertyValue) { if ( - $originalNode === null && empty($changedPropertyValue) + ($originalNode === null && empty($changedPropertyValue)) || ( isset($changeNodePropertiesDefaults[$propertyName]) && $changedPropertyValue === $changeNodePropertiesDefaults[$propertyName] diff --git a/Neos.Neos/Classes/Controller/Service/NodesController.php b/Neos.Neos/Classes/Controller/Service/NodesController.php index d8db9a2fcc7..bff9d28db9d 100644 --- a/Neos.Neos/Classes/Controller/Service/NodesController.php +++ b/Neos.Neos/Classes/Controller/Service/NodesController.php @@ -140,14 +140,12 @@ public function indexAction( 1645631728 ); } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $workspace->currentContentStreamId, + $subgraph = $contentRepository->getContentGraph(WorkspaceName::fromString($workspaceName))->getSubgraph( DimensionSpacePoint::fromLegacyDimensionArray($dimensions), VisibilityConstraints::withoutRestrictions() // we are in a backend controller. ); } else { - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $nodeAddress->contentStreamId, + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() // we are in a backend controller. ); @@ -211,9 +209,8 @@ public function showAction(string $identifier, string $workspaceName = 'live', a assert($workspace instanceof Workspace); $dimensionSpacePoint = DimensionSpacePoint::fromLegacyDimensionArray($dimensions); - $subgraph = $contentRepository->getContentGraph() + $subgraph = $contentRepository->getContentGraph(WorkspaceName::fromString($workspaceName)) ->getSubgraph( - $workspace->currentContentStreamId, $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); @@ -223,7 +220,7 @@ public function showAction(string $identifier, string $workspaceName = 'live', a if ($node === null) { $this->addExistingNodeVariantInformationToResponse( $nodeAggregateId, - $workspace->currentContentStreamId, + $workspace->workspaceName, $dimensionSpacePoint, $contentRepository ); @@ -280,17 +277,16 @@ public function createAction( ->findOneByName(WorkspaceName::fromString($workspaceName)); assert($workspace instanceof Workspace); - $sourceSubgraph = $contentRepository->getContentGraph() + $contentGraph = $contentRepository->getContentGraph(WorkspaceName::fromString($workspaceName)); + $sourceSubgraph = $contentGraph ->getSubgraph( - $workspace->currentContentStreamId, DimensionSpacePoint::fromLegacyDimensionArray($sourceDimensions), VisibilityConstraints::withoutRestrictions() ); $targetDimensionSpacePoint = DimensionSpacePoint::fromLegacyDimensionArray($dimensions); - $targetSubgraph = $contentRepository->getContentGraph() + $targetSubgraph = $contentGraph ->getSubgraph( - $workspace->currentContentStreamId, $targetDimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); @@ -323,13 +319,13 @@ public function createAction( */ protected function addExistingNodeVariantInformationToResponse( NodeAggregateId $identifier, - ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, DimensionSpacePoint $dimensionSpacePoint, ContentRepository $contentRepository ): void { - $contentGraph = $contentRepository->getContentGraph(); + $contentGraph = $contentRepository->getContentGraph($workspaceName); $nodeTypeManager = $contentRepository->getNodeTypeManager(); - $nodeAggregate = $contentGraph->findNodeAggregateById($contentStreamId, $identifier); + $nodeAggregate = $contentGraph->findNodeAggregateById($identifier); if ($nodeAggregate && $nodeAggregate->coveredDimensionSpacePoints->count() > 0) { $this->response->setHttpHeader('X-Neos-Node-Exists-In-Other-Dimensions', 'true'); @@ -346,7 +342,7 @@ protected function addExistingNodeVariantInformationToResponse( $missingNodesOnRootline = 0; while ( $parentAggregate = self::firstNodeAggregate( - $contentGraph->findParentNodeAggregates($contentStreamId, $identifier) + $contentGraph->findParentNodeAggregates($identifier) ) ) { if (!$parentAggregate->coversDimensionSpacePoint($dimensionSpacePoint)) { @@ -382,6 +378,12 @@ private static function firstNodeAggregate(iterable $nodeAggregates): ?NodeAggre /** * Adopt (translate) the given node and parents that are not yet visible to the given context * + * @param WorkspaceName $workspaceName + * @param NodeAggregateId $nodeAggregateId + * @param ContentSubgraphInterface $sourceSubgraph + * @param ContentSubgraphInterface $targetSubgraph + * @param DimensionSpacePoint $targetDimensionSpacePoint + * @param ContentRepository $contentRepository * @param boolean $copyContent true if the content from the nodes that are translated should be copied * @return void */ diff --git a/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php b/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php index a9b503611e5..34614a86683 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php +++ b/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php @@ -19,6 +19,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Neos\Domain\Model\Site; @@ -57,20 +58,19 @@ public function __construct( */ public function findSiteNodeBySite( Site $site, - ContentStreamId $contentStreamId, + WorkspaceName $workspaceName, DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints ): Node { $contentRepository = $this->contentRepositoryRegistry->get($site->getConfiguration()->contentRepositoryId); - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $contentStreamId, + $contentGraph = $contentRepository->getContentGraph($workspaceName); + $subgraph = $contentGraph->getSubgraph( $dimensionSpacePoint, $visibilityConstraints, ); - $rootNodeAggregate = $contentRepository->getContentGraph()->findRootNodeAggregateByType( - $contentStreamId, + $rootNodeAggregate = $contentGraph->findRootNodeAggregateByType( NodeTypeNameFactory::forSites() ); $rootNode = $rootNodeAggregate->getNodeByCoveredDimensionSpacePoint($dimensionSpacePoint); diff --git a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php index 7dbe858a5c5..33de72e820b 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php +++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php @@ -39,7 +39,7 @@ public function __construct( private ContentRepository $contentRepository, private InterDimensionalVariationGraph $interDimensionalVariationGraph, - private NodeTypeManager $nodeTypeManager + private NodeTypeManager $nodeTypeManager, ) { } @@ -54,15 +54,14 @@ public function removeSiteNode(SiteNodeName $siteNodeName): void 1651921482 ); } - $contentGraph = $this->contentRepository->getContentGraph(); foreach ($this->contentRepository->getWorkspaceFinder()->findAll() as $workspace) { + $contentGraph = $this->contentRepository->getContentGraph($workspace->workspaceName); $sitesNodeAggregate = $contentGraph->findRootNodeAggregateByType( - $workspace->currentContentStreamId, NodeTypeNameFactory::forSites() ); + $siteNodeAggregates = $contentGraph->findChildNodeAggregatesByName( - $workspace->currentContentStreamId, $sitesNodeAggregate->nodeAggregateId, $siteNodeName->toNodeName() ); @@ -99,8 +98,8 @@ public function createSiteNodeIfNotExists(Site $site, string $nodeTypeName): voi throw SiteNodeTypeIsInvalid::becauseItIsNotOfTypeSite(NodeTypeName::fromString($nodeTypeName)); } - $siteNodeAggregate = $this->contentRepository->getContentGraph()->findChildNodeAggregatesByName( - $liveWorkspace->currentContentStreamId, + $contentGraph = $this->contentRepository->getContentGraph($liveWorkspace->workspaceName); + $siteNodeAggregate = $contentGraph->findChildNodeAggregatesByName( $sitesNodeIdentifier, $site->getNodeName()->toNodeName(), ); diff --git a/Neos.Neos/Classes/Domain/Service/SiteServiceInternalsFactory.php b/Neos.Neos/Classes/Domain/Service/SiteServiceInternalsFactory.php index d47a105c5f1..123b324f754 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternalsFactory.php +++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternalsFactory.php @@ -27,7 +27,7 @@ public function build(ContentRepositoryServiceFactoryDependencies $serviceFactor return new SiteServiceInternals( $serviceFactoryDependencies->contentRepository, $serviceFactoryDependencies->interDimensionalVariationGraph, - $serviceFactoryDependencies->nodeTypeManager + $serviceFactoryDependencies->nodeTypeManager, ); } } diff --git a/Neos.Neos/Classes/Domain/Workspace/Workspace.php b/Neos.Neos/Classes/Domain/Workspace/Workspace.php index 670a4d9f7f7..814a897f158 100644 --- a/Neos.Neos/Classes/Domain/Workspace/Workspace.php +++ b/Neos.Neos/Classes/Domain/Workspace/Workspace.php @@ -262,8 +262,7 @@ private function requireNodeToBeOfType( NodeAggregateId $nodeAggregateId, NodeTypeName $nodeTypeName, ): void { - $nodeAggregate = $this->contentRepository->getContentGraph()->findNodeAggregateById( - $this->currentContentStreamId, + $nodeAggregate = $this->contentRepository->getContentGraph($this->name)->findNodeAggregateById( $nodeAggregateId, ); if (!$nodeAggregate instanceof NodeAggregate) { @@ -398,8 +397,7 @@ private function isChangePublishableWithinAncestorScope( } } - $subgraph = $this->contentRepository->getContentGraph()->getSubgraph( - $this->currentContentStreamId, + $subgraph = $this->contentRepository->getContentGraph($this->name)->getSubgraph( $change->originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index fce0ad83133..dbf85cfcd34 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -53,6 +53,8 @@ public function __construct( /** * Main entry point to *directly* flush the caches of a given NodeAggregate * + * FIXME workspaceName instead of contentStreamId + * * @param ContentRepository $contentRepository * @param ContentStreamId $contentStreamId * @param NodeAggregateId $nodeAggregateId @@ -67,8 +69,13 @@ public function flushNodeAggregate( $tagsToFlush[ContentCache::TAG_EVERYTHING] = 'which were tagged with "Everything".'; $this->registerChangeOnNodeIdentifier($contentRepository->id, $contentStreamId, $nodeAggregateId, $tagsToFlush); - $nodeAggregate = $contentRepository->getContentGraph()->findNodeAggregateById( - $contentStreamId, + + $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($contentStreamId); + if (is_null($workspace)) { + return; + } + $contentGraph = $contentRepository->getContentGraph($workspace->workspaceName); + $nodeAggregate = $contentGraph->findNodeAggregateById( $nodeAggregateId ); if (!$nodeAggregate) { @@ -87,8 +94,7 @@ public function flushNodeAggregate( $parentNodeAggregates = []; foreach ( - $contentRepository->getContentGraph()->findParentNodeAggregates( - $contentStreamId, + $contentGraph->findParentNodeAggregates( $nodeAggregateId ) as $parentNodeAggregate ) { @@ -122,8 +128,7 @@ public function flushNodeAggregate( ); foreach ( - $contentRepository->getContentGraph()->findParentNodeAggregates( - $nodeAggregate->contentStreamId, + $contentGraph->findParentNodeAggregates( $nodeAggregate->nodeAggregateId ) as $parentNodeAggregate ) { diff --git a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php index e26a5d526e9..76f9a024984 100644 --- a/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php +++ b/Neos.Neos/Classes/Fusion/Cache/GraphProjectorCatchUpHookForCacheFlushing.php @@ -140,13 +140,17 @@ public function onBeforeEvent(EventInterface $eventInstance, EventEnvelope $even // cleared, leading to presumably duplicate nodes in the UI. || $eventInstance instanceof NodeAggregateWasMoved ) { - $nodeAggregate = $this->contentRepository->getContentGraph()->findNodeAggregateById( - $eventInstance->getContentStreamId(), + $workspace = $this->contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($eventInstance->getContentStreamId()); + if ($workspace === null) { + return; + } + // FIXME: EventInterface->workspaceName + $contentGraph = $this->contentRepository->getContentGraph($workspace->workspaceName); + $nodeAggregate = $contentGraph->findNodeAggregateById( $eventInstance->getNodeAggregateId() ); if ($nodeAggregate) { - $parentNodeAggregates = $this->contentRepository->getContentGraph()->findParentNodeAggregates( - $nodeAggregate->contentStreamId, + $parentNodeAggregates = $contentGraph->findParentNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($parentNodeAggregates as $parentNodeAggregate) { @@ -173,8 +177,12 @@ public function onAfterEvent(EventInterface $eventInstance, EventEnvelope $event !($eventInstance instanceof NodeAggregateWasRemoved) && $eventInstance instanceof EmbedsContentStreamAndNodeAggregateId ) { - $nodeAggregate = $this->contentRepository->getContentGraph()->findNodeAggregateById( - $eventInstance->getContentStreamId(), + $workspace = $this->contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($eventInstance->getContentStreamId()); + if ($workspace === null) { + return; + } + // FIXME: EventInterface->workspaceName + $nodeAggregate = $this->contentRepository->getContentGraph($workspace->workspaceName)->findNodeAggregateById( $eventInstance->getNodeAggregateId() ); diff --git a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php index 03ca0a2352f..462aad66e6c 100644 --- a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php +++ b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php @@ -81,8 +81,7 @@ private function tryDeserializeNode(array $serializedNode): ?Node return null; } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $workspace->currentContentStreamId, + $subgraph = $contentRepository->getContentGraph($workspace->workspaceName)->getSubgraph( DimensionSpacePoint::fromArray($serializedNode['dimensionSpacePoint']), $workspace->isPublicWorkspace() ? VisibilityConstraints::frontend() diff --git a/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php index 5f2e128a252..42b514c6f8a 100644 --- a/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php @@ -7,6 +7,7 @@ use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Dimension\ContentDimensionId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; +use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; @@ -58,15 +59,21 @@ protected function buildItems(): array $interDimensionalVariationGraph = $dimensionMenuItemsImplementationInternals->interDimensionalVariationGraph; $currentDimensionSpacePoint = $currentNode->subgraphIdentity->dimensionSpacePoint; $contentDimensionIdentifierToLimitTo = $this->getContentDimensionIdentifierToLimitTo(); + // FIXME: node->workspaceName + $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($currentNode->subgraphIdentity->contentStreamId); + if (is_null($workspace)) { + return $menuItems; + } + $contentGraph = $contentRepository->getContentGraph($workspace->workspaceName); + foreach ($interDimensionalVariationGraph->getDimensionSpacePoints() as $dimensionSpacePoint) { $variant = null; if ($this->isDimensionSpacePointRelevant($dimensionSpacePoint)) { if ($dimensionSpacePoint->equals($currentDimensionSpacePoint)) { $variant = $currentNode; } else { - $variant = $contentRepository->getContentGraph() + $variant = $contentGraph ->getSubgraph( - $currentNode->subgraphIdentity->contentStreamId, $dimensionSpacePoint, $currentNode->subgraphIdentity->visibilityConstraints, ) @@ -79,7 +86,7 @@ protected function buildItems(): array $contentDimensionIdentifierToLimitTo, $currentNode->nodeAggregateId, $dimensionMenuItemsImplementationInternals, - $contentRepository + $contentGraph ); } @@ -147,7 +154,7 @@ protected function findClosestGeneralizationMatchingDimensionValue( ContentDimensionId $contentDimensionIdentifier, NodeAggregateId $nodeAggregateId, DimensionsMenuItemsImplementationInternals $dimensionMenuItemsImplementationInternals, - ContentRepository $contentRepository + ContentGraphInterface $contentGraph ): ?Node { $generalizations = $dimensionMenuItemsImplementationInternals->interDimensionalVariationGraph ->getWeightedGeneralizations($dimensionSpacePoint); @@ -157,11 +164,10 @@ protected function findClosestGeneralizationMatchingDimensionValue( $generalization->getCoordinate($contentDimensionIdentifier) === $dimensionSpacePoint->getCoordinate($contentDimensionIdentifier) ) { - $variant = $contentRepository->getContentGraph() + $variant = $contentGraph ->getSubgraph( - $this->currentNode->subgraphIdentity->contentStreamId, $generalization, - $this->currentNode->subgraphIdentity->visibilityConstraints, + $this->getCurrentNode()->subgraphIdentity->visibilityConstraints, ) ->findNodeById($nodeAggregateId); if ($variant) { diff --git a/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php b/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php index 15bafd94ac1..ddcf6d6b269 100644 --- a/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php @@ -144,10 +144,14 @@ public function findVariantInDimension(Node $node, ContentDimensionId|string $di $contentDimensionValue = is_string($dimensionValue) ? new ContentDimensionValue($dimensionValue) : $dimensionValue; $contentRepository = $this->contentRepositoryRegistry->get($node->subgraphIdentity->contentRepositoryId); + $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($node->subgraphIdentity->contentStreamId); + if (is_null($workspace)) { + return null; + } + // FIXME: node->workspaceName return $contentRepository - ->getContentGraph() + ->getContentGraph($workspace->workspaceName) ->getSubgraph( - $node->subgraphIdentity->contentStreamId, $node->subgraphIdentity->dimensionSpacePoint->vary($contentDimensionId, $contentDimensionValue->value), $node->subgraphIdentity->visibilityConstraints )->findNodeById($node->nodeAggregateId); diff --git a/Neos.Neos/Classes/Service/LinkingService.php b/Neos.Neos/Classes/Service/LinkingService.php index 445c1e62b63..74f1c01ca94 100644 --- a/Neos.Neos/Classes/Service/LinkingService.php +++ b/Neos.Neos/Classes/Service/LinkingService.php @@ -314,8 +314,7 @@ public function createNodeUri( $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); $nodeAddress = NodeAddressFactory::create($contentRepository)->createFromUriString($node); $workspace = $contentRepository->getWorkspaceFinder()->findOneByName($nodeAddress->workspaceName); - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $nodeAddress->contentStreamId, + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( $nodeAddress->dimensionSpacePoint, $workspace && !$workspace->isPublicWorkspace() ? VisibilityConstraints::withoutRestrictions() diff --git a/Neos.Neos/Classes/TypeConverter/HackyNodeAddressToNodeConverter.php b/Neos.Neos/Classes/TypeConverter/HackyNodeAddressToNodeConverter.php index 50069aed402..9786c418fc9 100644 --- a/Neos.Neos/Classes/TypeConverter/HackyNodeAddressToNodeConverter.php +++ b/Neos.Neos/Classes/TypeConverter/HackyNodeAddressToNodeConverter.php @@ -80,9 +80,8 @@ public function convertFrom( $nodeAddressFactory = NodeAddressFactory::create($contentRepository); $nodeAddress = $nodeAddressFactory->createFromUriString($source); - $subgraph = $contentRepository->getContentGraph() + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName) ->getSubgraph( - $nodeAddress->contentStreamId, $nodeAddress->dimensionSpacePoint, $nodeAddress->isInLiveWorkspace() ? VisibilityConstraints::frontend() diff --git a/Neos.Neos/Classes/View/FusionExceptionView.php b/Neos.Neos/Classes/View/FusionExceptionView.php index eb675b7310d..6a0fa255278 100644 --- a/Neos.Neos/Classes/View/FusionExceptionView.php +++ b/Neos.Neos/Classes/View/FusionExceptionView.php @@ -119,7 +119,7 @@ public function render(): ResponseInterface|StreamInterface if ($liveWorkspace && $site) { $currentSiteNode = $this->siteNodeUtility->findSiteNodeBySite( $site, - $liveWorkspace->currentContentStreamId, + $liveWorkspace->workspaceName, $dimensionSpacePoint, VisibilityConstraints::frontend() ); diff --git a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php index f75ef066aa9..dab61d2c975 100644 --- a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php @@ -277,9 +277,8 @@ public function render(): string } - $subgraph = $contentRepository->getContentGraph() + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName) ->getSubgraph( - $nodeAddress->contentStreamId, $nodeAddress->dimensionSpacePoint, $node->subgraphIdentity->visibilityConstraints ); @@ -370,8 +369,7 @@ private function resolveNodeAddressFromString( NodeAggregateId::fromString(\mb_substr($path, 7)) ); } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $documentNodeAddress->contentStreamId, + $subgraph = $contentRepository->getContentGraph($documentNodeAddress->workspaceName)->getSubgraph( $documentNodeAddress->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.Neos/Classes/ViewHelpers/Uri/NodeViewHelper.php b/Neos.Neos/Classes/ViewHelpers/Uri/NodeViewHelper.php index 67438d4afb5..1b3ef235890 100644 --- a/Neos.Neos/Classes/ViewHelpers/Uri/NodeViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/Uri/NodeViewHelper.php @@ -273,8 +273,7 @@ private function resolveNodeAddressFromString(string $path): ?NodeAddress NodeAggregateId::fromString(\mb_substr($path, 7)) ); } - $subgraph = $contentRepository->getContentGraph()->getSubgraph( - $documentNodeAddress->contentStreamId, + $subgraph = $contentRepository->getContentGraph($documentNodeAddress->workspaceName)->getSubgraph( $documentNodeAddress->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/BrowserTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/BrowserTrait.php index 030bde9ea19..8783cb9d480 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/BrowserTrait.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/BrowserTrait.php @@ -113,7 +113,7 @@ public function getCurrentNodeAddresses(): array */ public function iGetTheNodeAddressForNodeAggregate(string $rawNodeAggregateId, $alias = 'DEFAULT') { - $subgraph = $this->currentContentRepository->getContentGraph()->getSubgraph($this->currentContentStreamId, $this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); + $subgraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->getSubgraph($this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); $nodeAggregateId = NodeAggregateId::fromString($rawNodeAggregateId); $node = $subgraph->findNodeById($nodeAggregateId); Assert::assertNotNull($node, 'Did not find a node with aggregate id "' . $nodeAggregateId->value . '"'); @@ -136,7 +136,7 @@ public function iGetTheNodeAddressForNodeAggregate(string $rawNodeAggregateId, $ */ public function iGetTheNodeAddressForTheNodeAtPath(string $serializedNodePath, $alias = 'DEFAULT') { - $subgraph = $this->currentContentRepository->getContentGraph()->getSubgraph($this->currentContentStreamId, $this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); + $subgraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->getSubgraph($this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); if (!$this->getRootNodeAggregateId()) { throw new \Exception('ERROR: rootNodeAggregateId needed for running this step. You need to use "the event RootNodeAggregateWithNodeWasCreated was published with payload" to create a root node..'); } diff --git a/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php b/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php index 07bb0429ab9..70056016538 100644 --- a/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php +++ b/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php @@ -95,11 +95,10 @@ private function getNodesWithExceededDates(ContentRepository $contentRepository, foreach ($dimensionSpacePoints as $dimensionSpacePoint) { - $contentGraph = $contentRepository->getContentGraph(); + $contentGraph = $contentRepository->getContentGraph($liveWorkspace->workspaceName); // We fetch without restriction to get also all disabled nodes $subgraph = $contentGraph->getSubgraph( - $liveWorkspace->currentContentStreamId, $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); @@ -166,7 +165,7 @@ private function logResult(ChangedVisibility $result): void sprintf('Timed node visibility: %s node [NodeAggregateId: %s, DimensionSpacePoints: %s]: %s', $result->type->value, $result->node->nodeAggregateId->value, - join(',', $result->node->originDimensionSpacePoint->coordinates), + implode(',', $result->node->originDimensionSpacePoint->coordinates), $result->node->getLabel()) ); } diff --git a/Neos.TimeableNodeVisibility/Tests/Behavior/Bootstrap/FeatureContext.php b/Neos.TimeableNodeVisibility/Tests/Behavior/Bootstrap/FeatureContext.php index 66f948b239f..14907e8d62e 100644 --- a/Neos.TimeableNodeVisibility/Tests/Behavior/Bootstrap/FeatureContext.php +++ b/Neos.TimeableNodeVisibility/Tests/Behavior/Bootstrap/FeatureContext.php @@ -60,8 +60,7 @@ public function iHandleExceededNodeDates(): void public function iExpectThisNodeToBeEnabled(): void { Assert::assertNotNull($this->currentNode, 'No current node selected'); - $subgraph = $this->currentContentRepository->getContentGraph()->getSubgraph( - $this->currentContentStreamId, + $subgraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->getSubgraph( $this->currentDimensionSpacePoint, VisibilityConstraints::withoutRestrictions(), ); @@ -76,8 +75,7 @@ public function iExpectThisNodeToBeEnabled(): void public function iExpectThisNodeToBeDisabled(): void { Assert::assertNotNull($this->currentNode, 'No current node selected'); - $subgraph = $this->currentContentRepository->getContentGraph()->getSubgraph( - $this->currentContentStreamId, + $subgraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName)->getSubgraph( $this->currentDimensionSpacePoint, VisibilityConstraints::withoutRestrictions(), ); From 138abbd0f81acf44abb53a2c4c1fe7a3f8088aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCller?= Date: Mon, 6 May 2024 10:37:01 +0200 Subject: [PATCH 116/214] Apply suggestions from code review Co-authored-by: Bastian Waidelich --- .../src/ContentGraphFactory.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php index e4825f6cf0c..e32511db44e 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php @@ -20,7 +20,7 @@ * @internal only used inside the * @see ContentGraphFinder */ -readonly final class ContentGraphFactory implements ContentGraphFactoryInterface +final readonly class ContentGraphFactory implements ContentGraphFactoryInterface { public function __construct( private DbalClientInterface $client, @@ -37,13 +37,14 @@ public function buildForWorkspace(WorkspaceName $workspaceName): ContentGraph $tableName = strtolower(sprintf( 'cr_%s_p_%s', $this->contentRepositoryId->value, - 'Workspace' + 'workspace' )); $row = $this->client->getConnection()->executeQuery( ' SELECT * FROM ' . $tableName . ' WHERE workspaceName = :workspaceName + LIMIT 1 ', [ 'workspaceName' => $workspaceName->value, From baec4b8df27be58ed393575bc6248dd2001a81ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCller?= Date: Mon, 6 May 2024 11:18:19 +0200 Subject: [PATCH 117/214] Add comments to new ContentGraph methods Co-authored-by: Marc Henry Schultz <85400359+mhsdesign@users.noreply.github.com> --- .../Classes/Projection/ContentGraph/ContentGraphInterface.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 997d4dcf987..257361e960e 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -149,7 +149,9 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( */ public function countNodes(): int; + /** The workspace this content graph is operating on */ public function getWorkspaceName(): WorkspaceName; + /** @internal The content stream id where the workspace name points to for this instance */ public function getContentStreamId(): ContentStreamId; } From a24bab38769c9dd1e7c4d52a957961038b1f83fc Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Mon, 6 May 2024 12:00:05 +0200 Subject: [PATCH 118/214] TASK: Change visibility of method collectTagsForChangeOnNodeAggregate to private --- 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 a67e096ec16..216818735ed 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -86,7 +86,7 @@ public function flushNodeAggregate( /** * @return array */ - protected function collectTagsForChangeOnNodeAggregate( + private function collectTagsForChangeOnNodeAggregate( ContentRepository $contentRepository, ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId From 4a4b7c12f2f74fcc4c13731377ace15940c99c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Mon, 6 May 2024 13:41:47 +0200 Subject: [PATCH 119/214] Code cleanup and documentation --- .../src/ContentGraphFactory.php | 14 +++-- .../src/Domain/Repository/ContentGraph.php | 12 +++-- .../src/Domain/Repository/ContentSubgraph.php | 3 +- .../src/NodeQueryBuilder.php | 53 ++----------------- .../Classes/ContentGraphFinder.php | 35 +++++++++--- .../Classes/ContentGraphFinderInterface.php | 36 ------------- .../Classes/ContentRepository.php | 2 +- .../Bootstrap/CRTestSuiteRuntimeVariables.php | 4 +- .../Features/Bootstrap/ProjectedNodeTrait.php | 2 +- .../Service/AssetUsageSyncService.php | 2 +- 10 files changed, 58 insertions(+), 105 deletions(-) delete mode 100644 Neos.ContentRepository.Core/Classes/ContentGraphFinderInterface.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php index e32511db44e..81c7e2d30a5 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php @@ -1,5 +1,15 @@ first()) { + $rootNodeAggregate = $rootNodeAggregates->first(); + if ($rootNodeAggregate === null) { throw RootNodeAggregateDoesNotExist::butWasExpectedTo($nodeTypeName); } - return $rootNodeAggregates->first(); + return $rootNodeAggregate; } public function findRootNodeAggregates( @@ -308,7 +309,10 @@ public function countNodes(): int public function findUsedNodeTypeNames(): iterable { - return array_map(static fn (array $row) => NodeTypeName::fromString($row['nodetypename']), $this->nodeQueryBuilder->findUsedNodeTypeNames()); + return array_map( + static fn (array $row) => NodeTypeName::fromString($row['nodetypename']), + $this->fetchRows($this->nodeQueryBuilder->buildfindUsedNodeTypeNamesQuery()) + ); } /** @@ -354,11 +358,13 @@ private function fetchRows(QueryBuilder $queryBuilder): array } } + /** The workspace this content graph is operating on */ public function getWorkspaceName(): WorkspaceName { return $this->workspaceName; } + /** @internal The content stream id where the workspace name points to for this instance */ public function getContentStreamId(): ContentStreamId { return $this->contentStreamId; diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index d51537631e3..262d117d7c7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -157,7 +157,8 @@ public function countBackReferences(NodeAggregateId $nodeAggregateId, CountBackR public function findNodeById(NodeAggregateId $nodeAggregateId): ?Node { - $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeByIdQuery($nodeAggregateId, $this->contentStreamId, $this->dimensionSpacePoint); + $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeQuery($this->contentStreamId, $this->dimensionSpacePoint) + ->andWhere('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value); $this->addSubtreeTagConstraints($queryBuilder); return $this->fetchNode($queryBuilder); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index c04bb1c72f3..3b657ad82cf 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -101,12 +101,6 @@ public function buildBasicNodeQuery(ContentStreamId $contentStreamId, DimensionS ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } - public function buildBasicNodeByIdQuery(NodeAggregateId $nodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder - { - return $this->buildBasicNodeQuery($contentStreamId, $dimensionSpacePoint) - ->andWhere('n.nodeaggregateid = :nodeAggregateId')->setParameter('nodeAggregateId', $nodeAggregateId->value); - } - public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { return $this->createQueryBuilder() @@ -260,54 +254,15 @@ private function searchPropertyValueStatement(QueryBuilder $queryBuilder, Proper return 'JSON_SEARCH(' . $nodeTableAlias . '.properties, \'one\', :' . $paramName . ', NULL, \'$.' . $escapedPropertyName . '.value\') IS NOT NULL'; } - public function getTablenameForNode(): string - { - return $this->tableNamePrefix . '_node'; - } - - public function getTablenameForHierachyRelation(): string - { - return $this->tableNamePrefix . '_hierarchyrelation'; - } - - public function getTablenameForDimensionSpacePoints(): string - { - return $this->tableNamePrefix . '_dimensionspacepoints'; - } - - public function getTablenameForReferenceRelation(): string - { - return $this->tableNamePrefix . '_referencerelation'; - } - /** - * @return array> + * @return QueryBuilder * @throws DBALException */ - public function findUsedNodeTypeNames(): array + public function buildfindUsedNodeTypeNamesQuery(): QueryBuilder { - $rows = $this->fetchRows($this->createQueryBuilder() + return $this->createQueryBuilder() ->select('DISTINCT nodetypename') - ->from($this->contentGraphTableNames->node())); - - return $rows; - } - - /** - * @return array> - * @throws DBALException - */ - public function fetchRows(QueryBuilder $queryBuilder): array - { - $result = $queryBuilder->execute(); - if (!$result instanceof Result) { - throw new \RuntimeException(sprintf('Failed to execute query. Expected result to be of type %s, got: %s', Result::class, get_debug_type($result)), 1701443535); - } - try { - return $result->fetchAllAssociative(); - } catch (DriverException $e) { - throw new \RuntimeException(sprintf('Failed to fetch rows from database: %s', $e->getMessage()), 1701444358, $e); - } + ->from($this->contentGraphTableNames->node()); } private function createQueryBuilder(): QueryBuilder diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php b/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php index 2d148e8d962..8d8bcc52ab1 100644 --- a/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php +++ b/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php @@ -15,6 +15,7 @@ namespace Neos\ContentRepository\Core; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; +use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -23,10 +24,13 @@ /** * A finder for ContentGraphInterface bound to contentStream/Workspace * - * @see ContentGraphInterface - * @api only for read access during write operations and in services + * Userland code should not use this directly. You should get a ContentGraph + * via ContentRepository::getContentGraph() + * + * @api Not for userland code, only for read access during write operations and in services + * @see ContentRepository::getContentGraph() */ -final class ContentGraphFinder implements ContentGraphFinderInterface +final class ContentGraphFinder implements ProjectionStateInterface { /** * @var array @@ -39,10 +43,15 @@ public function __construct( } /** + * The default way to get a content graph to operate on. + * The currently assigned ContentStreamId for the given Workspace is resolved internally. + * * @throws WorkspaceDoesNotExist if there is no workspace with the provided name * @throws ContentStreamDoesNotExistYet if the provided workspace does not resolve to an existing content stream + * @api + * @see ContentRepository::getContentGraph() */ - public function fromWorkspaceName(WorkspaceName $workspaceName): ContentGraphInterface + public function getByWorkspaceName(WorkspaceName $workspaceName): ContentGraphInterface { if (isset($this->contenGraphInstances[$workspaceName->value])) { return $this->contenGraphInstances[$workspaceName->value]; @@ -53,25 +62,36 @@ public function fromWorkspaceName(WorkspaceName $workspaceName): ContentGraphInt } /** + * Access runtime caches for implementation specific flush operations + * * @return ContentGraphInterface[] + * @internal only for flushing runtime caches in adapter implementations, should not be needed anywhere else. */ public function getInstances(): array { return $this->contenGraphInstances; } + /** + * To release all held instances, in case a workspace/content stream relation needs to be reset + * + * @return void + * @internal Should only be needed after write operations (which should take care on their own) + */ public function reset(): void { $this->contenGraphInstances = []; } /** + * For testing we allow getting an instance set by both parameters, effectively overriding the relationship at will + * * @param WorkspaceName $workspaceName * @param ContentStreamId $contentStreamId * @return ContentGraphInterface - * @internal + * @internal Only for testing */ - public function fromWorkspaceNameAndContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface + public function getByWorkspaceNameAndContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface { if (isset($this->contenGraphInstances[$workspaceName->value]) && $this->contenGraphInstances[$workspaceName->value]->getContentStreamId() === $contentStreamId) { return $this->contenGraphInstances[$workspaceName->value]; @@ -86,7 +106,8 @@ public function fromWorkspaceNameAndContentStreamId(WorkspaceName $workspaceName * override ContentStreamId and vice versa resolving the WorkspaceName from this ContentStreamId should result in the * given WorkspaceName within the closure. * - * @internal + * @internal Used in write operations applying commands to a contentstream that will have WorkspaceName in the future + * but doesn't have one yet. */ public function overrideContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId, \Closure $fn): void { diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFinderInterface.php b/Neos.ContentRepository.Core/Classes/ContentGraphFinderInterface.php deleted file mode 100644 index d934734d53e..00000000000 --- a/Neos.ContentRepository.Core/Classes/ContentGraphFinderInterface.php +++ /dev/null @@ -1,36 +0,0 @@ -projectionState(ContentGraphFinder::class)->fromWorkspaceName($workspaceName); + return $this->projectionState(ContentGraphFinder::class)->getByWorkspaceName($workspaceName); } public function getWorkspaceFinder(): WorkspaceFinder diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php index 89f9f15cf67..1e5e3c31658 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php @@ -164,10 +164,10 @@ public function getCurrentSubgraph(): ContentSubgraphInterface $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); $contentGraphFinder->reset(); if (isset($this->currentContentStreamId)) { - return $contentGraphFinder->fromWorkspaceNameAndContentStreamId($this->currentWorkspaceName, $this->currentContentStreamId)->getSubgraph($this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); + return $contentGraphFinder->getByWorkspaceNameAndContentStreamId($this->currentWorkspaceName, $this->currentContentStreamId)->getSubgraph($this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); } - return $contentGraphFinder->fromWorkspaceName($this->currentWorkspaceName)->getSubgraph( + return $contentGraphFinder->getByWorkspaceName($this->currentWorkspaceName)->getSubgraph( $this->currentDimensionSpacePoint, $this->currentVisibilityConstraints ); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index 7d6d00f32dc..3c712a68eaa 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -269,7 +269,7 @@ protected function initializeCurrentNodeFromContentGraph(callable $query): void $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); $contentGraphFinder->reset(); if (isset($this->currentContentStreamId)) { - $contentGraph = $contentGraphFinder->fromWorkspaceNameAndContentStreamId($this->currentWorkspaceName, $this->currentContentStreamId); + $contentGraph = $contentGraphFinder->getByWorkspaceNameAndContentStreamId($this->currentWorkspaceName, $this->currentContentStreamId); } else { $contentGraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName); } diff --git a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php index e00e9b33014..bf965135951 100644 --- a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php +++ b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php @@ -63,7 +63,7 @@ public function isAssetUsageStillValid(AssetUsage $usage): bool if (is_null($workspace)) { return false; } - $subGraph = $this->contentGraphFinder->fromWorkspaceName($workspace->workspaceName) ->getSubgraph( + $subGraph = $this->contentGraphFinder->getByWorkspaceName($workspace->workspaceName) ->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); From 2345e3d2099657ab556d18fea58d6acdf0aa2f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Mon, 6 May 2024 14:28:48 +0200 Subject: [PATCH 120/214] Fix linter issue --- .../src/ContentGraphTableNames.php | 4 ++-- .../src/NodeQueryBuilder.php | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php index f810e070d6f..6d0611886cf 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -6,9 +6,9 @@ * Encapsulates table name generation for content graph tables * @internal */ -final class ContentGraphTableNames +final readonly class ContentGraphTableNames { - private function __construct(private readonly string $tableNamePrefix) + private function __construct(private string $tableNamePrefix) { } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index 3b657ad82cf..42225dc29cc 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -3,11 +3,9 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Driver\Exception as DriverException; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\QueryBuilder; -use Doctrine\DBAL\Result; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindRootNodeAggregatesFilter; @@ -33,13 +31,13 @@ /** * @internal Implementation detail of the DoctrineDbalAdapter */ -class NodeQueryBuilder +final readonly class NodeQueryBuilder { - public readonly ContentGraphTableNames $contentGraphTableNames; + public ContentGraphTableNames $contentGraphTableNames; public function __construct( - private readonly Connection $connection, - private readonly string $tableNamePrefix + private Connection $connection, + string $tableNamePrefix ) { $this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); } From 5d8bdf5f234319140713f47ed34c7b87c1e5ea59 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Mon, 6 May 2024 13:40:12 +0000 Subject: [PATCH 121/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index db950b52fb7..21e629f6c3f 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-05-01 +The following reference was automatically generated from code on 2024-05-06 .. _`Neos Command Reference: NEOS.FLOW`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index bfea0f360ff..7d62820bfc2 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-05-01 +This reference was automatically generated from code on 2024-05-06 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index 5656681f720..20bdc8a7e87 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-05-01 +This reference was automatically generated from code on 2024-05-06 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index 4adef5d951f..3926453820e 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-05-01 +This reference was automatically generated from code on 2024-05-06 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index ea2f404c592..d3ff40b5705 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-05-01 +This reference was automatically generated from code on 2024-05-06 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index 8aa200eb4c9..3ced4268b34 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-05-01 +This reference was automatically generated from code on 2024-05-06 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From e047578f686a718d56353caff1003c135926d17b Mon Sep 17 00:00:00 2001 From: Denny Lubitz Date: Mon, 6 May 2024 18:37:06 +0200 Subject: [PATCH 122/214] TASK: Change loading of objects notation in behat tests --- .../Behavior/Features/Bootstrap/FeatureContext.php | 11 ++++++----- .../Behavior/Features/ContentCache/Assets.feature | 14 +++++++------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php index f8b699afcf7..631f0469d7f 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/FeatureContext.php @@ -113,20 +113,21 @@ protected function createContentRepository( protected function deserializeProperties(array $properties): PropertyValuesToWrite { $properties = array_map( - $this->loadFlowObjectsRecursive(...), + $this->loadObjectsRecursive(...), $properties ); return $this->deserializePropertiesCrTestSuiteTrait($properties); } - public function loadFlowObjectsRecursive(mixed $value): mixed + private function loadObjectsRecursive(mixed $value): mixed { - if (is_array($value) && isset($value['__flow_object_type'])) { - return $this->persistenceManager->getObjectByIdentifier($value['__identifier'], $value['__flow_object_type'], true); + if (is_string($value) && str_starts_with($value, 'Asset:')) { + $assetIdentier = substr($value, strlen('Asset:')); + return $this->persistenceManager->getObjectByIdentifier($assetIdentier, 'Neos\\Media\\Domain\\Model\\Asset', true); } elseif (is_array($value)) { return array_map( - $this->loadFlowObjectsRecursive(...), + $this->loadObjectsRecursive(...), $value ); } diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature index 583ba992e7c..9ede1e5a1e8 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature @@ -60,13 +60,13 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on asset changes | nodeTypeName | "Neos.Neos:Sites" | And the graph projection is fully up to date And the following CreateNodeAggregateWithNode commands are executed: - | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | - | a | root | Neos.Neos:Site | {} | site | - | a1 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1", "title": "Node a1", "asset": {"__flow_object_type":"Neos\\Media\\Domain\\Model\\Asset","__identifier":"an-asset-to-change"}} | a1 | - | a1-1 | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1-1", "title": "Node a1-1", "assets": [{"__flow_object_type":"Neos\\Media\\Domain\\Model\\Asset","__identifier":"an-asset-to-change"}]} | a1-1 | - | a1-2 | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1-2", "title": "Node a1-2", "asset": {"__flow_object_type":"Neos\\Media\\Domain\\Model\\Asset","__identifier":"some-other-asset"}} | a1-2 | - | a2 | a | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a2", "title": "Node a2", "text": "Link to asset://an-asset-to-change."} | a2 | - | a3 | a | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a2", "title": "Node a2", "text": "Link to asset://some-other-asset."} | a3 | + | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | + | a | root | Neos.Neos:Site | {} | site | + | a1 | a | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1", "title": "Node a1", "asset": "Asset:an-asset-to-change"} | a1 | + | a1-1 | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1-1", "title": "Node a1-1", "assets": ["Asset:an-asset-to-change"]} | a1-1 | + | a1-2 | a1 | Neos.Neos:Test.DocumentType1 | {"uriPathSegment": "a1-2", "title": "Node a1-2", "asset": "Asset:some-other-asset"} | a1-2 | + | a2 | a | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a2", "title": "Node a2", "text": "Link to asset://an-asset-to-change."} | a2 | + | a3 | a | Neos.Neos:Test.DocumentType2 | {"uriPathSegment": "a2", "title": "Node a2", "text": "Link to asset://some-other-asset."} | a3 | When the command RebaseWorkspace is executed with payload: | Key | Value | | workspaceName | "user-test" | From 145458807738ab2abd39322b3853662441931891 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Mon, 6 May 2024 19:11:06 +0200 Subject: [PATCH 123/214] 4993 - Adjust descendant hierarchy join --- .../src/Domain/Projection/Feature/SubtreeTagging.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 7e0cbc2564a..25887ab2575 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -167,8 +167,7 @@ private function moveSubtreeTags( JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.childnodeanchor - WHERE - dh.contentstreamid = :contentStreamId + AND dh.contentstreamid = :contentStreamId AND dh.dimensionspacepointhash = :dimensionSpacePointHash ) SELECT * FROM cte From 24cfa06d01200ca7f19d7243b4fab59cb6ebca0a Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Wed, 8 May 2024 09:06:47 +0200 Subject: [PATCH 124/214] Tweak ContentGraphFinder runtime cache --- .../DoctrineDbalContentGraphProjection.php | 17 ++-------- .../Classes/ContentGraphFinder.php | 34 ++++++++----------- .../Feature/WorkspaceCommandHandler.php | 2 +- .../Bootstrap/CRTestSuiteRuntimeVariables.php | 2 +- .../Features/Bootstrap/ProjectedNodeTrait.php | 2 +- 5 files changed, 20 insertions(+), 37 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 0e5ad53cd74..fb23e1060d9 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -157,14 +157,7 @@ public function reset(): void $this->checkpointStorage->acquireLock(); $this->checkpointStorage->updateAndReleaseLock(SequenceNumber::none()); - - $contentGraphFinder = $this->getState(); - /** @var ContentGraph $contentGraph */ - foreach ($contentGraphFinder->getInstances() as $contentGraph) { - foreach ($contentGraph->getSubgraphs() as $subgraph) { - $subgraph->inMemoryCache->enable(); - } - } + $this->getState()->forgetInstances(); } private function truncateDatabaseTables(): void @@ -237,13 +230,7 @@ public function getState(): ContentGraphFinder public function markStale(): void { - $contentGraphFinder = $this->getState(); - /** @var ContentGraph $contentGraph */ - foreach ($contentGraphFinder->getInstances() as $contentGraph) { - foreach ($contentGraph->getSubgraphs() as $subgraph) { - $subgraph->inMemoryCache->disable(); - } - } + $this->getState()->forgetInstances(); } /** diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php b/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php index 2d148e8d962..8e6f609c531 100644 --- a/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php +++ b/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php @@ -31,7 +31,7 @@ final class ContentGraphFinder implements ContentGraphFinderInterface /** * @var array */ - private array $contenGraphInstances = []; + private array $contentGraphInstances = []; public function __construct( private readonly ContentGraphFactoryInterface $contentGraphFactory @@ -44,25 +44,21 @@ public function __construct( */ public function fromWorkspaceName(WorkspaceName $workspaceName): ContentGraphInterface { - if (isset($this->contenGraphInstances[$workspaceName->value])) { - return $this->contenGraphInstances[$workspaceName->value]; + if (isset($this->contentGraphInstances[$workspaceName->value])) { + return $this->contentGraphInstances[$workspaceName->value]; } - $this->contenGraphInstances[$workspaceName->value] = $this->contentGraphFactory->buildForWorkspace($workspaceName); - return $this->contenGraphInstances[$workspaceName->value]; + $this->contentGraphInstances[$workspaceName->value] = $this->contentGraphFactory->buildForWorkspace($workspaceName); + return $this->contentGraphInstances[$workspaceName->value]; } /** - * @return ContentGraphInterface[] + * Remove runtime cache.. + * @internal */ - public function getInstances(): array - { - return $this->contenGraphInstances; - } - - public function reset(): void + public function forgetInstances(): void { - $this->contenGraphInstances = []; + $this->contentGraphInstances = []; } /** @@ -73,8 +69,8 @@ public function reset(): void */ public function fromWorkspaceNameAndContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface { - if (isset($this->contenGraphInstances[$workspaceName->value]) && $this->contenGraphInstances[$workspaceName->value]->getContentStreamId() === $contentStreamId) { - return $this->contenGraphInstances[$workspaceName->value]; + if (isset($this->contentGraphInstances[$workspaceName->value]) && $this->contentGraphInstances[$workspaceName->value]->getContentStreamId() === $contentStreamId) { + return $this->contentGraphInstances[$workspaceName->value]; } return $this->contentGraphFactory->buildForWorkspaceAndContentStream($workspaceName, $contentStreamId); @@ -91,15 +87,15 @@ public function fromWorkspaceNameAndContentStreamId(WorkspaceName $workspaceName public function overrideContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId, \Closure $fn): void { $contentGraph = $this->contentGraphFactory->buildForWorkspaceAndContentStream($workspaceName, $contentStreamId); - $replacedAdapter = $this->contenGraphInstances[$workspaceName->value] ?? null; - $this->contenGraphInstances[$workspaceName->value] = $contentGraph; + $replacedAdapter = $this->contentGraphInstances[$workspaceName->value] ?? null; + $this->contentGraphInstances[$workspaceName->value] = $contentGraph; try { $fn(); } finally { - unset($this->contenGraphInstances[$workspaceName->value]); + unset($this->contentGraphInstances[$workspaceName->value]); if ($replacedAdapter) { - $this->contenGraphInstances[$workspaceName->value] = $replacedAdapter; + $this->contentGraphInstances[$workspaceName->value] = $replacedAdapter; } } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index a53f499be11..9c87c0f2480 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -1017,6 +1017,6 @@ private function hasEventsInContentStreamExceptForking( private function resetContentGraphCache(ContentRepository $contentRepository): void { - $contentRepository->projectionState(ContentGraphFinder::class)->reset(); + $contentRepository->projectionState(ContentGraphFinder::class)->forgetInstances(); } } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php index 89f9f15cf67..5cb09bd9e81 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php @@ -162,7 +162,7 @@ public function visibilityConstraintsAreSetTo(string $restrictionType): void public function getCurrentSubgraph(): ContentSubgraphInterface { $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); - $contentGraphFinder->reset(); + $contentGraphFinder->forgetInstances(); if (isset($this->currentContentStreamId)) { return $contentGraphFinder->fromWorkspaceNameAndContentStreamId($this->currentWorkspaceName, $this->currentContentStreamId)->getSubgraph($this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index 7d6d00f32dc..372c19dca27 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -267,7 +267,7 @@ public function iExpectThisNodeToExactlyInheritTheTags(string $tagList): void protected function initializeCurrentNodeFromContentGraph(callable $query): void { $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); - $contentGraphFinder->reset(); + $contentGraphFinder->forgetInstances(); if (isset($this->currentContentStreamId)) { $contentGraph = $contentGraphFinder->fromWorkspaceNameAndContentStreamId($this->currentWorkspaceName, $this->currentContentStreamId); } else { From d5257e735e0b6624162c697114fa9455adb037a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Wed, 8 May 2024 15:29:57 +0200 Subject: [PATCH 125/214] Separate CommandHandlingDependencies --- .../DoctrineDbalContentGraphProjection.php | 9 +- .../Classes/CommandHandler/CommandBus.php | 5 +- .../CommandHandlerInterface.php | 3 +- .../Classes/CommandHandlingDependencies.php | 75 +++++++ .../Classes/ContentGraphFinder.php | 29 --- .../Classes/ContentRepository.php | 5 +- .../Feature/Common/ConstraintChecks.php | 12 +- .../Feature/ContentStreamCommandHandler.php | 57 +++--- .../DimensionSpaceCommandHandler.php | 15 +- .../Feature/NodeAggregateCommandHandler.php | 40 ++-- .../Feature/NodeCreation/NodeCreation.php | 14 +- .../Feature/NodeDisabling/NodeDisabling.php | 22 +-- .../NodeDuplicationCommandHandler.php | 16 +- .../NodeModification/NodeModification.php | 17 +- .../Classes/Feature/NodeMove/NodeMove.php | 15 +- .../NodeReferencing/NodeReferencing.php | 19 +- .../Feature/NodeRemoval/NodeRemoval.php | 10 +- .../Feature/NodeRenaming/NodeRenaming.php | 8 +- .../Feature/NodeTypeChange/NodeTypeChange.php | 8 +- .../Feature/NodeVariation/NodeVariation.php | 10 +- .../RootNodeCreation/RootNodeHandling.php | 18 +- .../Feature/SubtreeTagging/SubtreeTagging.php | 10 +- .../Feature/WorkspaceCommandHandler.php | 184 ++++++++---------- .../ContentGraph/ContentGraphProjection.php | 10 +- .../Features/Bootstrap/ProjectedNodeTrait.php | 1 - 25 files changed, 323 insertions(+), 289 deletions(-) create mode 100644 Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index fb23e1060d9..b57799b16e7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -14,7 +14,6 @@ use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint; -use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ContentGraph; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph; use Neos\ContentRepository\Core\ContentGraphFinder; @@ -53,7 +52,6 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Timestamps; use Neos\ContentRepository\Core\Projection\ProjectionInterface; use Neos\ContentRepository\Core\Projection\ProjectionStatus; -use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; @@ -65,7 +63,7 @@ * @implements ProjectionInterface * @internal but the graph projection is api */ -final class DoctrineDbalContentGraphProjection implements ProjectionInterface, WithMarkStaleInterface +final class DoctrineDbalContentGraphProjection implements ProjectionInterface { use NodeVariation; use SubtreeTagging; @@ -228,11 +226,6 @@ public function getState(): ContentGraphFinder return $this->contentGraphFinder; } - public function markStale(): void - { - $this->getState()->forgetInstances(); - } - /** * @throws \Throwable */ diff --git a/Neos.ContentRepository.Core/Classes/CommandHandler/CommandBus.php b/Neos.ContentRepository.Core/Classes/CommandHandler/CommandBus.php index 1ce93c44e86..b77ce854cf6 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandler/CommandBus.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandler/CommandBus.php @@ -4,6 +4,7 @@ namespace Neos\ContentRepository\Core\CommandHandler; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\EventStore\EventsToPublish; @@ -25,12 +26,12 @@ public function __construct(CommandHandlerInterface ...$handlers) $this->handlers = $handlers; } - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { // TODO fail if multiple handlers can handle the same command foreach ($this->handlers as $handler) { if ($handler->canHandle($command)) { - return $handler->handle($command, $contentRepository); + return $handler->handle($command, $commandHandlingDependencies); } } throw new \RuntimeException(sprintf('No handler found for Command "%s"', get_debug_type($command)), 1649582778); diff --git a/Neos.ContentRepository.Core/Classes/CommandHandler/CommandHandlerInterface.php b/Neos.ContentRepository.Core/Classes/CommandHandler/CommandHandlerInterface.php index 4a8ca5a5b5f..e190337c370 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandler/CommandHandlerInterface.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandler/CommandHandlerInterface.php @@ -4,6 +4,7 @@ namespace Neos\ContentRepository\Core\CommandHandler; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\EventStore\EventsToPublish; @@ -18,5 +19,5 @@ interface CommandHandlerInterface { public function canHandle(CommandInterface $command): bool; - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish; + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish; } diff --git a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php new file mode 100644 index 00000000000..da04fcf752f --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php @@ -0,0 +1,75 @@ +value to ContentGraphInterface + * @var array + */ + private array $overridenContentGraphInstances = []; + + public function __construct(private readonly ContentRepository $contentRepository) + { + } + + public function handle(CommandInterface $command): CommandResult + { + return $this->contentRepository->handle($command); + } + + public function getWorkspaceFinder(): WorkspaceFinder + { + return $this->contentRepository->getWorkspaceFinder(); + } + + public function getContentStreamFinder(): ContentStreamFinder + { + return $this->contentRepository->getContentStreamFinder(); + } + + public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface + { + if (isset($this->overridenContentGraphInstances[$workspaceName->value])) { + return $this->overridenContentGraphInstances[$workspaceName->value]; + } + + return $this->contentRepository->getContentGraph($workspaceName); + } + + /** + * Stateful (dirty) override of the chosen ContentStreamId for a given workspace, it applies within the given closure. + * Implementations must ensure that requesting the contentStreamId for this workspace will resolve to the given + * override ContentStreamId and vice versa resolving the WorkspaceName from this ContentStreamId should result in the + * given WorkspaceName within the closure. + * + * @internal Used in write operations applying commands to a contentstream that will have WorkspaceName in the future + * but doesn't have one yet. + */ + public function overrideContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId, \Closure $fn): void + { + if (isset($this->overridenContentGraphInstances[$workspaceName->value])) { + throw new \RuntimeException('Contentstream override for this workspace already in effect, nesting not allowed.', 1715170938); + } + + $contentGraph = $this->contentRepository->projectionState(ContentGraphFinder::class)->getByWorkspaceNameAndContentStreamId($workspaceName, $contentStreamId); + $this->overridenContentGraphInstances[$workspaceName->value] = $contentGraph; + + try { + $fn(); + } finally { + unset($this->overridenContentGraphInstances[$workspaceName->value]); + } + } +} diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php b/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php index 704516dd8d4..38d00320864 100644 --- a/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php +++ b/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php @@ -80,35 +80,6 @@ public function forgetInstances(): void */ public function getByWorkspaceNameAndContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface { - if (isset($this->contentGraphInstances[$workspaceName->value]) && $this->contentGraphInstances[$workspaceName->value]->getContentStreamId() === $contentStreamId) { - return $this->contentGraphInstances[$workspaceName->value]; - } - return $this->contentGraphFactory->buildForWorkspaceAndContentStream($workspaceName, $contentStreamId); } - - /** - * Stateful (dirty) override of the chosen ContentStreamId for a given workspace, it applies within the given closure. - * Implementations must ensure that requesting the contentStreamId for this workspace will resolve to the given - * override ContentStreamId and vice versa resolving the WorkspaceName from this ContentStreamId should result in the - * given WorkspaceName within the closure. - * - * @internal Used in write operations applying commands to a contentstream that will have WorkspaceName in the future - * but doesn't have one yet. - */ - public function overrideContentStreamId(WorkspaceName $workspaceName, ContentStreamId $contentStreamId, \Closure $fn): void - { - $contentGraph = $this->contentGraphFactory->buildForWorkspaceAndContentStream($workspaceName, $contentStreamId); - $replacedAdapter = $this->contentGraphInstances[$workspaceName->value] ?? null; - $this->contentGraphInstances[$workspaceName->value] = $contentGraph; - - try { - $fn(); - } finally { - unset($this->contentGraphInstances[$workspaceName->value]); - if ($replacedAdapter) { - $this->contentGraphInstances[$workspaceName->value] = $replacedAdapter; - } - } - } } diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index 82dd42e5db0..859364ef96a 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -64,6 +64,8 @@ final class ContentRepository */ private array $projectionStateCache; + private CommandHandlingDependencies $commandHandlingDependencies; + /** * @internal use the {@see ContentRepositoryFactory::getOrBuild()} to instantiate @@ -81,6 +83,7 @@ public function __construct( private readonly UserIdProviderInterface $userIdProvider, private readonly ClockInterface $clock, ) { + $this->commandHandlerToBeNamed = new CommandHandlingDependencies($this); } /** @@ -97,7 +100,7 @@ public function handle(CommandInterface $command): CommandResult { // the commands only calculate which events they want to have published, but do not do the // publishing themselves - $eventsToPublish = $this->commandBus->handle($command, $this); + $eventsToPublish = $this->commandBus->handle($command, $this->commandHandlerToBeNamed); // TODO meaningful exception message $initiatingUserId = $this->userIdProvider->getUserId(); diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index a012a8357a6..5af6ec79133 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\Common; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; @@ -81,10 +81,10 @@ abstract protected function getAllowedDimensionSubspace(): DimensionSpacePointSe */ protected function requireContentStream( WorkspaceName $workspaceName, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): ContentStreamId { - $contentStreamId = $contentRepository->getContentGraph($workspaceName)->getContentStreamId(); - $state = $contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId); + $contentStreamId = $commandHandlingDependencies->getContentGraph($workspaceName)->getContentStreamId(); + $state = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($contentStreamId); if ($state === null) { throw new ContentStreamDoesNotExistYet( 'Content stream for "' . $workspaceName->value . '" does not exist yet.', @@ -731,11 +731,11 @@ protected function validateReferenceProperties( protected function getExpectedVersionOfContentStream( ContentStreamId $contentStreamId, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): ExpectedVersion { return ExpectedVersion::fromVersion( - $contentRepository->getContentStreamFinder()->findVersionForContentStream($contentStreamId)->unwrap() + $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($contentStreamId)->unwrap() ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php index 6aac5d47e9f..3b3547fa9f9 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; @@ -49,14 +50,14 @@ public function canHandle(CommandInterface $command): bool return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); } - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { return match ($command::class) { CreateContentStream::class => $this->handleCreateContentStream($command), - CloseContentStream::class => $this->handleCloseContentStream($command, $contentRepository), - ReopenContentStream::class => $this->handleReopenContentStream($command, $contentRepository), - ForkContentStream::class => $this->handleForkContentStream($command, $contentRepository), - RemoveContentStream::class => $this->handleRemoveContentStream($command, $contentRepository), + CloseContentStream::class => $this->handleCloseContentStream($command, $commandHandlingDependencies), + ReopenContentStream::class => $this->handleReopenContentStream($command, $commandHandlingDependencies), + ForkContentStream::class => $this->handleForkContentStream($command, $commandHandlingDependencies), + RemoveContentStream::class => $this->handleRemoveContentStream($command, $commandHandlingDependencies), default => throw new \DomainException('Cannot handle commands of class ' . get_class($command), 1710408206), }; } @@ -83,11 +84,11 @@ private function handleCreateContentStream( private function handleCloseContentStream( CloseContentStream $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStreamToExist($command->contentStreamId, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $contentRepository); - $this->requireContentStreamToNotBeClosed($command->contentStreamId, $contentRepository); + $this->requireContentStreamToExist($command->contentStreamId, $commandHandlingDependencies); + $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $commandHandlingDependencies); + $this->requireContentStreamToNotBeClosed($command->contentStreamId, $commandHandlingDependencies); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->contentStreamId)->getEventStreamName(); return new EventsToPublish( @@ -103,11 +104,11 @@ private function handleCloseContentStream( private function handleReopenContentStream( ReopenContentStream $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStreamToExist($command->contentStreamId, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $contentRepository); - $this->requireContentStreamToBeClosed($command->contentStreamId, $contentRepository); + $this->requireContentStreamToExist($command->contentStreamId, $commandHandlingDependencies); + $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $commandHandlingDependencies); + $this->requireContentStreamToBeClosed($command->contentStreamId, $commandHandlingDependencies); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->contentStreamId)->getEventStreamName(); return new EventsToPublish( @@ -128,13 +129,13 @@ private function handleReopenContentStream( */ private function handleForkContentStream( ForkContentStream $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStreamToExist($command->sourceContentStreamId, $contentRepository); - $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId, $contentRepository); + $this->requireContentStreamToExist($command->sourceContentStreamId, $commandHandlingDependencies); + $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId, $commandHandlingDependencies); // TOOD: THis is not great - $sourceContentStreamVersion = $contentRepository->getContentStreamFinder()->findVersionForContentStream($command->sourceContentStreamId); + $sourceContentStreamVersion = $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($command->sourceContentStreamId); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->newContentStreamId) ->getEventStreamName(); @@ -155,10 +156,10 @@ private function handleForkContentStream( private function handleRemoveContentStream( RemoveContentStream $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStreamToExist($command->contentStreamId, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $contentRepository); + $this->requireContentStreamToExist($command->contentStreamId, $commandHandlingDependencies); + $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $commandHandlingDependencies); $streamName = ContentStreamEventStreamName::fromContentStreamId( $command->contentStreamId @@ -181,9 +182,9 @@ private function handleRemoveContentStream( */ protected function requireContentStreamToExist( ContentStreamId $contentStreamId, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): void { - $maybeVersion = $contentRepository->getContentStreamFinder()->findVersionForContentStream($contentStreamId); + $maybeVersion = $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($contentStreamId); if ($maybeVersion->isNothing()) { throw new ContentStreamDoesNotExistYet( 'Content stream "' . $contentStreamId->value . '" does not exist yet.', @@ -194,9 +195,9 @@ protected function requireContentStreamToExist( protected function requireContentStreamToNotBeClosed( ContentStreamId $contentStreamId, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): void { - $contentStreamState = $contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId); + $contentStreamState = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($contentStreamId); if ($contentStreamState === ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsClosed( 'Content stream "' . $contentStreamId->value . '" is closed.', @@ -207,9 +208,9 @@ protected function requireContentStreamToNotBeClosed( protected function requireContentStreamToBeClosed( ContentStreamId $contentStreamId, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): void { - $contentStreamState = $contentRepository->getContentStreamFinder()->findStateForContentStream($contentStreamId); + $contentStreamState = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($contentStreamId); if ($contentStreamState !== ContentStreamState::STATE_CLOSED) { throw new ContentStreamIsNotClosed( 'Content stream "' . $contentStreamId->value . '" is not closed.', @@ -220,9 +221,9 @@ protected function requireContentStreamToBeClosed( protected function getExpectedVersionOfContentStream( ContentStreamId $contentStreamId, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): ExpectedVersion { - $maybeVersion = $contentRepository->getContentStreamFinder()->findVersionForContentStream($contentStreamId); + $maybeVersion = $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($contentStreamId); return ExpectedVersion::fromVersion( $maybeVersion ->unwrap() diff --git a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php index 1fd62aa0af7..dadb48a00cf 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/DimensionSpaceAdjustment/DimensionSpaceCommandHandler.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\ContentDimensionZookeeper; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; @@ -51,20 +52,20 @@ public function canHandle(CommandInterface $command): bool return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); } - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { /** @phpstan-ignore-next-line */ return match ($command::class) { - MoveDimensionSpacePoint::class => $this->handleMoveDimensionSpacePoint($command, $contentRepository), - AddDimensionShineThrough::class => $this->handleAddDimensionShineThrough($command, $contentRepository), + MoveDimensionSpacePoint::class => $this->handleMoveDimensionSpacePoint($command, $commandHandlingDependencies), + AddDimensionShineThrough::class => $this->handleAddDimensionShineThrough($command, $commandHandlingDependencies), }; } private function handleMoveDimensionSpacePoint( MoveDimensionSpacePoint $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); $streamName = ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(); @@ -89,9 +90,9 @@ private function handleMoveDimensionSpacePoint( private function handleAddDimensionShineThrough( AddDimensionShineThrough $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); $streamName = ContentStreamEventStreamName::fromContentStreamId($contentGraph->getContentStreamId()) ->getEventStreamName(); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php index ee9bb692ad3..3830cf78e29 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; @@ -52,9 +53,6 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\SubtreeTagging; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; -use Neos\ContentRepository\Core\Projection\ContentStream\ContentStreamFinder; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * @internal from userland, you'll use ContentRepository::handle to dispatch commands @@ -93,33 +91,33 @@ public function canHandle(CommandInterface $command): bool return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); } - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { /** @phpstan-ignore-next-line */ return match ($command::class) { - SetNodeProperties::class => $this->handleSetNodeProperties($command, $contentRepository), + SetNodeProperties::class => $this->handleSetNodeProperties($command, $commandHandlingDependencies), SetSerializedNodeProperties::class - => $this->handleSetSerializedNodeProperties($command, $contentRepository), - SetNodeReferences::class => $this->handleSetNodeReferences($command, $contentRepository), + => $this->handleSetSerializedNodeProperties($command, $commandHandlingDependencies), + SetNodeReferences::class => $this->handleSetNodeReferences($command, $commandHandlingDependencies), SetSerializedNodeReferences::class - => $this->handleSetSerializedNodeReferences($command, $contentRepository), - ChangeNodeAggregateType::class => $this->handleChangeNodeAggregateType($command, $contentRepository), - RemoveNodeAggregate::class => $this->handleRemoveNodeAggregate($command, $contentRepository), + => $this->handleSetSerializedNodeReferences($command, $commandHandlingDependencies), + ChangeNodeAggregateType::class => $this->handleChangeNodeAggregateType($command, $commandHandlingDependencies), + RemoveNodeAggregate::class => $this->handleRemoveNodeAggregate($command, $commandHandlingDependencies), CreateNodeAggregateWithNode::class - => $this->handleCreateNodeAggregateWithNode($command, $contentRepository), + => $this->handleCreateNodeAggregateWithNode($command, $commandHandlingDependencies), CreateNodeAggregateWithNodeAndSerializedProperties::class - => $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($command, $contentRepository), - MoveNodeAggregate::class => $this->handleMoveNodeAggregate($command, $contentRepository), - CreateNodeVariant::class => $this->handleCreateNodeVariant($command, $contentRepository), + => $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($command, $commandHandlingDependencies), + MoveNodeAggregate::class => $this->handleMoveNodeAggregate($command, $commandHandlingDependencies), + CreateNodeVariant::class => $this->handleCreateNodeVariant($command, $commandHandlingDependencies), CreateRootNodeAggregateWithNode::class - => $this->handleCreateRootNodeAggregateWithNode($command, $contentRepository), + => $this->handleCreateRootNodeAggregateWithNode($command, $commandHandlingDependencies), UpdateRootNodeAggregateDimensions::class - => $this->handleUpdateRootNodeAggregateDimensions($command, $contentRepository), - DisableNodeAggregate::class => $this->handleDisableNodeAggregate($command, $contentRepository), - EnableNodeAggregate::class => $this->handleEnableNodeAggregate($command, $contentRepository), - TagSubtree::class => $this->handleTagSubtree($command, $contentRepository), - UntagSubtree::class => $this->handleUntagSubtree($command, $contentRepository), - ChangeNodeAggregateName::class => $this->handleChangeNodeAggregateName($command, $contentRepository), + => $this->handleUpdateRootNodeAggregateDimensions($command, $commandHandlingDependencies), + DisableNodeAggregate::class => $this->handleDisableNodeAggregate($command, $commandHandlingDependencies), + EnableNodeAggregate::class => $this->handleEnableNodeAggregate($command, $commandHandlingDependencies), + TagSubtree::class => $this->handleTagSubtree($command, $commandHandlingDependencies), + UntagSubtree::class => $this->handleUntagSubtree($command, $commandHandlingDependencies), + ChangeNodeAggregateName::class => $this->handleChangeNodeAggregateName($command, $commandHandlingDependencies), }; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index 3f288366ff2..35c9a8589b9 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeCreation; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\EventStore\Events; @@ -69,7 +69,7 @@ abstract protected function getNodeTypeManager(): NodeTypeManager; private function handleCreateNodeAggregateWithNode( CreateNodeAggregateWithNode $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { $this->requireNodeType($command->nodeTypeName); $this->validateProperties($command->initialPropertyValues, $command->nodeTypeName); @@ -91,7 +91,7 @@ private function handleCreateNodeAggregateWithNode( $lowLevelCommand = $lowLevelCommand->withTetheredDescendantNodeAggregateIds($command->tetheredDescendantNodeAggregateIds); } - return $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($lowLevelCommand, $contentRepository); + return $this->handleCreateNodeAggregateWithNodeAndSerializedProperties($lowLevelCommand, $commandHandlingDependencies); } private function validateProperties(?PropertyValuesToWrite $propertyValues, NodeTypeName $nodeTypeName): void @@ -124,11 +124,11 @@ private function validateProperties(?PropertyValuesToWrite $propertyValues, Node */ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( CreateNodeAggregateWithNodeAndSerializedProperties $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStream($command->workspaceName, $contentRepository); - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $this->requireDimensionSpacePointToExist($command->originDimensionSpacePoint->toDimensionSpacePoint()); $nodeType = $this->requireNodeType($command->nodeTypeName); $this->requireNodeTypeToNotBeAbstract($nodeType); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php index 8c673fd535e..c74d9fce8c0 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDisabling/NodeDisabling.php @@ -14,7 +14,7 @@ * source code. */ -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; use Neos\ContentRepository\Core\EventStore\Events; @@ -26,11 +26,8 @@ use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasTagged; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasUntagged; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; -use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateCurrentlyDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * @internal implementation detail of Command Handlers @@ -41,16 +38,16 @@ abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\ /** * @param DisableNodeAggregate $command - * @param ContentRepository $contentRepository + * @param CommandHandlingDependencies $commandHandlingDependencies * @return EventsToPublish */ private function handleDisableNodeAggregate( DisableNodeAggregate $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStream($command->workspaceName, $contentRepository); - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, @@ -94,6 +91,7 @@ private function handleDisableNodeAggregate( /** * @param EnableNodeAggregate $command + * @param CommandHandlingDependencies $commandHandlingDependencies * @return EventsToPublish * @throws ContentStreamDoesNotExistYet * @throws DimensionSpacePointNotFound @@ -101,10 +99,10 @@ private function handleDisableNodeAggregate( */ public function handleEnableNodeAggregate( EnableNodeAggregate $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php index a069297ea90..a160b112561 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeDuplication/NodeDuplicationCommandHandler.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeDuplication; -use Neos\ContentRepository\Core\NodeType\NodeType; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; @@ -54,9 +54,9 @@ public function __construct( ) { } - protected function getContentGraph(WorkspaceName $workspaceName, ContentRepository $contentRepository): ContentGraphInterface + protected function getContentGraph(WorkspaceName $workspaceName, CommandHandlingDependencies $commandHandlingDependencies): ContentGraphInterface { - return $contentRepository->getContentGraph($workspaceName); + return $commandHandlingDependencies->getContentGraph($workspaceName); } protected function getNodeTypeManager(): NodeTypeManager @@ -74,11 +74,11 @@ public function canHandle(CommandInterface $command): bool return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); } - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { /** @phpstan-ignore-next-line */ return match ($command::class) { - CopyNodesRecursively::class => $this->handleCopyNodesRecursively($command, $contentRepository), + CopyNodesRecursively::class => $this->handleCopyNodesRecursively($command, $commandHandlingDependencies), }; } @@ -87,11 +87,11 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo */ private function handleCopyNodesRecursively( CopyNodesRecursively $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { // Basic constraints (Content Stream / Dimension Space Point / Node Type of to-be-inserted root node) - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $this->requireDimensionSpacePointToExist( $command->targetDimensionSpacePoint->toDimensionSpacePoint() ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php index d331a28d86c..9098fd31034 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeModification; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\NodeAggregateEventPublisher; @@ -31,6 +31,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\PropertyNames; + /** * @internal implementation detail of Command Handlers */ @@ -45,10 +46,10 @@ abstract protected function requireProjectedNodeAggregate( private function handleSetNodeProperties( SetNodeProperties $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStream($command->workspaceName, $contentRepository); - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); $this->requireDimensionSpacePointToExist($command->originDimensionSpacePoint->toDimensionSpacePoint()); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, @@ -70,15 +71,15 @@ private function handleSetNodeProperties( $command->propertyValues->getPropertiesToUnset() ); - return $this->handleSetSerializedNodeProperties($lowLevelCommand, $contentRepository); + return $this->handleSetSerializedNodeProperties($lowLevelCommand, $commandHandlingDependencies); } private function handleSetSerializedNodeProperties( SetSerializedNodeProperties $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); // Check if node exists $nodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php index d5d1e59627b..1869aca61de 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeMove/NodeMove.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeMove; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; @@ -41,7 +41,6 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateIsNoSibling; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** * @internal implementation detail of Command Handlers @@ -82,11 +81,11 @@ abstract protected function requireNodeAggregateToBeChild( */ private function handleMoveNodeAggregate( MoveNodeAggregate $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $contentStreamId = $this->requireContentStream($command->workspaceName, $contentRepository); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $contentStreamId = $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentStreamId, $commandHandlingDependencies); $this->requireDimensionSpacePointToExist($command->dimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, @@ -192,8 +191,8 @@ private function handleMoveNodeAggregate( $command->newParentNodeAggregateId, $command->newSucceedingSiblingNodeAggregateId, $command->newPrecedingSiblingNodeAggregateId, - $command->newParentNodeAggregateId !== null - || ($command->newSucceedingSiblingNodeAggregateId === null) && ($command->newPrecedingSiblingNodeAggregateId === null), + ($command->newParentNodeAggregateId !== null) + || (($command->newSucceedingSiblingNodeAggregateId === null) && ($command->newPrecedingSiblingNodeAggregateId === null)), ) ) ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php index 6d1cd7c06e5..9134d910691 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeReferencing/NodeReferencing.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeReferencing; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\ConstraintChecks; @@ -28,6 +28,7 @@ use Neos\ContentRepository\Core\Feature\NodeReferencing\Event\NodeReferencesWereSet; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; +use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; /** @@ -45,10 +46,10 @@ abstract protected function requireProjectedNodeAggregate( private function handleSetNodeReferences( SetNodeReferences $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStream($command->workspaceName, $contentRepository); - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); $this->requireDimensionSpacePointToExist($command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint()); $sourceNodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, @@ -87,18 +88,18 @@ private function handleSetNodeReferences( )), ); - return $this->handleSetSerializedNodeReferences($lowLevelCommand, $contentRepository); + return $this->handleSetSerializedNodeReferences($lowLevelCommand, $commandHandlingDependencies); } /** - * @throws \Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet + * @throws ContentStreamDoesNotExistYet */ private function handleSetSerializedNodeReferences( SetSerializedNodeReferences $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $this->requireDimensionSpacePointToExist( $command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint() ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php index edd3d2bc919..41a8c5c7eed 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRemoval/NodeRemoval.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeRemoval; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; use Neos\ContentRepository\Core\EventStore\Events; @@ -47,11 +47,11 @@ abstract protected function areAncestorNodeTypeConstraintChecksEnabled(): bool; */ private function handleRemoveNodeAggregate( RemoveNodeAggregate $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStream($command->workspaceName, $contentRepository); - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, $command->nodeAggregateId diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php index ea378e1e230..930d1294f1d 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeRenaming; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\ConstraintChecks; @@ -30,10 +30,10 @@ trait NodeRenaming { use ConstraintChecks; - private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command, ContentRepository $contentRepository): EventsToPublish + private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, $command->nodeAggregateId diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php index 2c49ced70d5..341d64437a1 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeTypeChange; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePointSet; @@ -104,14 +104,14 @@ abstract protected function createEventsForMissingTetheredNode( */ private function handleChangeNodeAggregateType( ChangeNodeAggregateType $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { /************** * Constraint checks **************/ // existence of content stream, node type and node aggregate - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $newNodeType = $this->requireNodeType($command->newNodeTypeName); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php index 4e14662ae9b..fe9a58f7d3e 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeVariation/NodeVariation.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\NodeVariation; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace\Exception\DimensionSpacePointNotFound; use Neos\ContentRepository\Core\EventStore\EventsToPublish; use Neos\ContentRepository\Core\Feature\Common\ConstraintChecks; @@ -48,11 +48,11 @@ trait NodeVariation */ private function handleCreateNodeVariant( CreateNodeVariant $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStream($command->workspaceName, $contentRepository); - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, $command->nodeAggregateId diff --git a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php index 936529907e6..8bd2e3cb142 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php +++ b/Neos.ContentRepository.Core/Classes/Feature/RootNodeCreation/RootNodeHandling.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\Feature\RootNodeCreation; -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\EventStore\Events; @@ -59,7 +59,7 @@ abstract protected function requireNodeTypeToBeOfTypeRoot(NodeType $nodeType): v /** * @param CreateRootNodeAggregateWithNode $command - * @param ContentRepository $contentRepository + * @param CommandHandlingDependencies $commandHandlingDependencies * @return EventsToPublish * @throws ContentStreamDoesNotExistYet * @throws NodeAggregateCurrentlyExists @@ -69,11 +69,11 @@ abstract protected function requireNodeTypeToBeOfTypeRoot(NodeType $nodeType): v */ private function handleCreateRootNodeAggregateWithNode( CreateRootNodeAggregateWithNode $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireContentStream($command->workspaceName, $contentRepository); - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $this->requireProjectedNodeAggregateToNotExist( $contentGraph, $command->nodeAggregateId @@ -145,10 +145,10 @@ private function createRootWithNode( */ private function handleUpdateRootNodeAggregateDimensions( UpdateRootNodeAggregateDimensions $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $contentRepository); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, $command->nodeAggregateId diff --git a/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php b/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php index 64f0ce05b4c..d16e300dfe5 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php +++ b/Neos.ContentRepository.Core/Classes/Feature/SubtreeTagging/SubtreeTagging.php @@ -14,7 +14,7 @@ * source code. */ -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\DimensionSpace; use Neos\ContentRepository\Core\EventStore\Events; use Neos\ContentRepository\Core\EventStore\EventsToPublish; @@ -36,9 +36,9 @@ trait SubtreeTagging abstract protected function getInterDimensionalVariationGraph(): DimensionSpace\InterDimensionalVariationGraph; - private function handleTagSubtree(TagSubtree $command, ContentRepository $contentRepository): EventsToPublish + private function handleTagSubtree(TagSubtree $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate($contentGraph, $command->nodeAggregateId); $this->requireNodeAggregateToCoverDimensionSpacePoint( @@ -78,9 +78,9 @@ private function handleTagSubtree(TagSubtree $command, ContentRepository $conten ); } - public function handleUntagSubtree(UntagSubtree $command, ContentRepository $contentRepository): EventsToPublish + public function handleUntagSubtree(UntagSubtree $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); $this->requireDimensionSpacePointToExist($command->coveredDimensionSpacePoint); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index 9c87c0f2480..4ebdadd94d0 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -17,7 +17,7 @@ use Neos\ContentRepository\Core\CommandHandler\CommandHandlerInterface; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\CommandHandler\CommandResult; -use Neos\ContentRepository\Core\ContentGraphFinder; +use Neos\ContentRepository\Core\CommandHandlingDependencies; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\EventStore\DecoratedEvent; use Neos\ContentRepository\Core\EventStore\EventInterface; @@ -98,21 +98,21 @@ public function canHandle(CommandInterface $command): bool return method_exists($this, 'handle' . (new \ReflectionClass($command))->getShortName()); } - public function handle(CommandInterface $command, ContentRepository $contentRepository): EventsToPublish + public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { /** @phpstan-ignore-next-line */ return match ($command::class) { - CreateWorkspace::class => $this->handleCreateWorkspace($command, $contentRepository), - RenameWorkspace::class => $this->handleRenameWorkspace($command, $contentRepository), - CreateRootWorkspace::class => $this->handleCreateRootWorkspace($command, $contentRepository), - PublishWorkspace::class => $this->handlePublishWorkspace($command, $contentRepository), - RebaseWorkspace::class => $this->handleRebaseWorkspace($command, $contentRepository), - PublishIndividualNodesFromWorkspace::class => $this->handlePublishIndividualNodesFromWorkspace($command, $contentRepository), - DiscardIndividualNodesFromWorkspace::class => $this->handleDiscardIndividualNodesFromWorkspace($command, $contentRepository), - DiscardWorkspace::class => $this->handleDiscardWorkspace($command, $contentRepository), - DeleteWorkspace::class => $this->handleDeleteWorkspace($command, $contentRepository), - ChangeWorkspaceOwner::class => $this->handleChangeWorkspaceOwner($command, $contentRepository), - ChangeBaseWorkspace::class => $this->handleChangeBaseWorkspace($command, $contentRepository), + CreateWorkspace::class => $this->handleCreateWorkspace($command, $commandHandlingDependencies), + RenameWorkspace::class => $this->handleRenameWorkspace($command, $commandHandlingDependencies), + CreateRootWorkspace::class => $this->handleCreateRootWorkspace($command, $commandHandlingDependencies), + PublishWorkspace::class => $this->handlePublishWorkspace($command, $commandHandlingDependencies), + RebaseWorkspace::class => $this->handleRebaseWorkspace($command, $commandHandlingDependencies), + PublishIndividualNodesFromWorkspace::class => $this->handlePublishIndividualNodesFromWorkspace($command, $commandHandlingDependencies), + DiscardIndividualNodesFromWorkspace::class => $this->handleDiscardIndividualNodesFromWorkspace($command, $commandHandlingDependencies), + DiscardWorkspace::class => $this->handleDiscardWorkspace($command, $commandHandlingDependencies), + DeleteWorkspace::class => $this->handleDeleteWorkspace($command, $commandHandlingDependencies), + ChangeWorkspaceOwner::class => $this->handleChangeWorkspaceOwner($command, $commandHandlingDependencies), + ChangeBaseWorkspace::class => $this->handleChangeBaseWorkspace($command, $commandHandlingDependencies), }; } @@ -124,10 +124,10 @@ public function handle(CommandInterface $command, ContentRepository $contentRepo */ private function handleCreateWorkspace( CreateWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { try { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); $contentStreamId = $contentGraph->getContentStreamId(); } catch (ContentStreamDoesNotExistYet $e) { // Desired outcome @@ -139,7 +139,7 @@ private function handleCreateWorkspace( $command->workspaceName->value ), 1505830958921); - $baseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($command->baseWorkspaceName); + $baseWorkspace = $commandHandlingDependencies->getWorkspaceFinder()->findOneByName($command->baseWorkspaceName); if ($baseWorkspace === null) { throw new BaseWorkspaceDoesNotExist(sprintf( 'The workspace %s (base workspace of %s) does not exist', @@ -148,9 +148,9 @@ private function handleCreateWorkspace( ), 1513890708); } - $baseWorkspaceContentGraph = $contentRepository->getContentGraph($command->baseWorkspaceName); + $baseWorkspaceContentGraph = $commandHandlingDependencies->getContentGraph($command->baseWorkspaceName); // When the workspace is created, we first have to fork the content stream - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->newContentStreamId, $baseWorkspaceContentGraph->getContentStreamId(), @@ -180,9 +180,9 @@ private function handleCreateWorkspace( */ private function handleRenameWorkspace( RenameWorkspace $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); + $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); $events = Events::with( new WorkspaceWasRenamed( @@ -207,10 +207,10 @@ private function handleRenameWorkspace( */ private function handleCreateRootWorkspace( CreateRootWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { try { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); $contentStreamId = $contentGraph->getContentStreamId(); } catch (ContentStreamDoesNotExistYet $e) { // Desired outcome @@ -223,7 +223,7 @@ private function handleCreateRootWorkspace( ), 1505848624450); $newContentStreamId = $command->newContentStreamId; - $contentRepository->handle( + $commandHandlingDependencies->handle( CreateContentStream::create( $newContentStreamId, ) @@ -256,10 +256,10 @@ private function handleCreateRootWorkspace( */ private function handlePublishWorkspace( PublishWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); $this->publishContentStream( $workspace->currentContentStreamId, @@ -267,7 +267,7 @@ private function handlePublishWorkspace( )?->block(); // After publishing a workspace, we need to again fork from Base. - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->newContentStreamId, $baseWorkspace->currentContentStreamId, @@ -284,7 +284,6 @@ private function handlePublishWorkspace( ) ); - $this->resetContentGraphCache($contentRepository); // if we got so far without an Exception, we can switch the Workspace's active Content stream. return new EventsToPublish( $streamName, @@ -372,25 +371,25 @@ private function publishContentStream( */ private function handleRebaseWorkspace( RebaseWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; - $oldWorkspaceContentStreamIdState = $contentRepository->getContentStreamFinder() + $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamFinder() ->findStateForContentStream($oldWorkspaceContentStreamId); if ($oldWorkspaceContentStreamIdState === null) { throw new \DomainException('Cannot rebase a workspace with a stateless content stream', 1711718314); } // 0) close old content stream - $contentRepository->handle( + $commandHandlingDependencies->handle( CloseContentStream::create($oldWorkspaceContentStreamId) )->block(); // 1) fork a new content stream $rebasedContentStreamId = $command->rebasedContentStreamId; - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->rebasedContentStreamId, $baseWorkspace->currentContentStreamId, @@ -405,14 +404,14 @@ private function handleRebaseWorkspace( // 2) extract the commands from the to-be-rebased content stream; and applies them on the new content stream $originalCommands = $this->extractCommandsFromContentStreamMetadata($workspaceContentStreamName); $commandsThatFailed = new CommandsThatFailedDuringRebase(); - $contentRepository->projectionState(ContentGraphFinder::class)->overrideContentStreamId( + $commandHandlingDependencies->overrideContentStreamId( $command->workspaceName, $command->rebasedContentStreamId, - function () use ($originalCommands, $contentRepository, &$commandsThatFailed): void { + function () use ($originalCommands, $commandHandlingDependencies, &$commandsThatFailed): void { foreach ($originalCommands as $sequenceNumber => $originalCommand) { // We no longer need to adjust commands as the workspace stays the same try { - $contentRepository->handle($originalCommand)->block(); + $commandHandlingDependencies->handle($originalCommand)->block(); // if we came this far, we know the command was applied successfully. } catch (\Exception $e) { $commandsThatFailed = $commandsThatFailed->add( @@ -437,7 +436,6 @@ function () use ($originalCommands, $contentRepository, &$commandsThatFailed): v ), ); - $this->resetContentGraphCache($contentRepository); return new EventsToPublish( $workspaceStreamName, $events, @@ -446,7 +444,7 @@ function () use ($originalCommands, $contentRepository, &$commandsThatFailed): v } // 3.E) In case of an exception, reopen the old content stream... - $contentRepository->handle( + $commandHandlingDependencies->handle( ReopenContentStream::create( $oldWorkspaceContentStreamId, $oldWorkspaceContentStreamIdState, @@ -454,11 +452,10 @@ function () use ($originalCommands, $contentRepository, &$commandsThatFailed): v )->block(); // ... remove the newly created one... - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $rebasedContentStreamId ))->block(); - $this->resetContentGraphCache($contentRepository); // ...and throw an exception that contains all the information about what exactly failed throw new WorkspaceRebaseFailed($commandsThatFailed, 'Rebase failed', 1711713880); } @@ -508,19 +505,19 @@ private function extractCommandsFromContentStreamMetadata( */ private function handlePublishIndividualNodesFromWorkspace( PublishIndividualNodesFromWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); $oldWorkspaceContentStreamId = $workspace->currentContentStreamId; - $oldWorkspaceContentStreamIdState = $contentRepository->getContentStreamFinder()->findStateForContentStream($oldWorkspaceContentStreamId); + $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($oldWorkspaceContentStreamId); if ($oldWorkspaceContentStreamIdState === null) { throw new \DomainException('Cannot publish nodes on a workspace with a stateless content stream', 1710410114); } - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); // 1) close old content stream - $contentRepository->handle( + $commandHandlingDependencies->handle( CloseContentStream::create($contentGraph->getContentStreamId()) ); @@ -533,7 +530,7 @@ private function handlePublishIndividualNodesFromWorkspace( /** @var array $remainingCommands */ // 3) fork a new contentStream, based on the base WS, and apply MATCHING - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->contentStreamIdForMatchingPart, $baseWorkspace->currentContentStreamId, @@ -542,10 +539,10 @@ private function handlePublishIndividualNodesFromWorkspace( try { // 4) using the new content stream, apply the matching commands - $contentRepository->projectionState(ContentGraphFinder::class)->overrideContentStreamId( + $commandHandlingDependencies->overrideContentStreamId( $baseWorkspace->workspaceName, $command->contentStreamIdForMatchingPart, - function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { + function () use ($matchingCommands, $commandHandlingDependencies, $baseWorkspace): void { foreach ($matchingCommands as $matchingCommand) { if (!($matchingCommand instanceof RebasableToOtherWorkspaceInterface)) { throw new \RuntimeException( @@ -554,7 +551,7 @@ function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { ); } - $contentRepository->handle($matchingCommand->createCopyForWorkspace( + $commandHandlingDependencies->handle($matchingCommand->createCopyForWorkspace( $baseWorkspace->workspaceName, ))->block(); } @@ -568,60 +565,59 @@ function () use ($matchingCommands, $contentRepository, $baseWorkspace): void { )?->block(); // 6) fork a new content stream, based on the base WS, and apply REST - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->contentStreamIdForRemainingPart, $baseWorkspace->currentContentStreamId ) )->block(); + // 7) apply REMAINING commands to the workspace's new content stream - $contentRepository->projectionState(ContentGraphFinder::class)->overrideContentStreamId( + $commandHandlingDependencies->overrideContentStreamId( $command->workspaceName, $command->contentStreamIdForRemainingPart, - function () use ($contentRepository, $remainingCommands) { + function () use ($commandHandlingDependencies, $remainingCommands) { foreach ($remainingCommands as $remainingCommand) { - $contentRepository->handle($remainingCommand)->block(); + $commandHandlingDependencies->handle($remainingCommand)->block(); } } ); } catch (\Exception $exception) { // 4.E) In case of an exception, reopen the old content stream and remove the newly created - $contentRepository->handle( + $commandHandlingDependencies->handle( ReopenContentStream::create( $oldWorkspaceContentStreamId, $oldWorkspaceContentStreamIdState, ) )->block(); - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $command->contentStreamIdForMatchingPart ))->block(); try { - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $command->contentStreamIdForRemainingPart ))->block(); } catch (ContentStreamDoesNotExistYet $contentStreamDoesNotExistYet) { // in case the exception was thrown before 6), this does not exist } - $this->resetContentGraphCache($contentRepository); throw $exception; } // 8) to avoid dangling content streams, we need to remove our temporary content stream (whose events // have already been published) as well as the old one - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $command->contentStreamIdForMatchingPart )); - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $oldWorkspaceContentStreamId )); $streamName = WorkspaceEventStreamName::fromWorkspaceName($command->workspaceName)->getEventStreamName(); - $this->resetContentGraphCache($contentRepository); return new EventsToPublish( $streamName, Events::fromArray([ @@ -649,19 +645,19 @@ function () use ($contentRepository, $remainingCommands) { */ private function handleDiscardIndividualNodesFromWorkspace( DiscardIndividualNodesFromWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $contentGraph = $contentRepository->getContentGraph($command->workspaceName); - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); + $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); $oldWorkspaceContentStreamId = $contentGraph->getContentStreamId(); - $oldWorkspaceContentStreamIdState = $contentRepository->getContentStreamFinder()->findStateForContentStream($contentGraph->getContentStreamId()); + $oldWorkspaceContentStreamIdState = $commandHandlingDependencies->getContentStreamFinder()->findStateForContentStream($contentGraph->getContentStreamId()); if ($oldWorkspaceContentStreamIdState === null) { throw new \DomainException('Cannot discard nodes on a workspace with a stateless content stream', 1710408112); } - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); // 1) close old content stream - $contentRepository->handle( + $commandHandlingDependencies->handle( CloseContentStream::create($oldWorkspaceContentStreamId) )->block(); @@ -674,7 +670,7 @@ private function handleDiscardIndividualNodesFromWorkspace( $this->separateMatchingAndRemainingCommands($command, $workspace, $commandsToDiscard, $commandsToKeep); // 3) fork a new contentStream, based on the base WS, and apply the commands to keep - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->newContentStreamId, $baseWorkspace->currentContentStreamId, @@ -683,10 +679,10 @@ private function handleDiscardIndividualNodesFromWorkspace( // 4) using the new content stream, apply the commands to keep try { - $contentRepository->projectionState(ContentGraphFinder::class)->overrideContentStreamId( + $commandHandlingDependencies->overrideContentStreamId( $baseWorkspace->workspaceName, $command->newContentStreamId, - function () use ($commandsToKeep, $contentRepository, $baseWorkspace): void { + function () use ($commandsToKeep, $commandHandlingDependencies, $baseWorkspace): void { foreach ($commandsToKeep as $matchingCommand) { if (!($matchingCommand instanceof RebasableToOtherWorkspaceInterface)) { throw new \RuntimeException( @@ -695,7 +691,7 @@ function () use ($commandsToKeep, $contentRepository, $baseWorkspace): void { ); } - $contentRepository->handle($matchingCommand->createCopyForWorkspace( + $commandHandlingDependencies->handle($matchingCommand->createCopyForWorkspace( $baseWorkspace->workspaceName, ))->block(); } @@ -703,29 +699,27 @@ function () use ($commandsToKeep, $contentRepository, $baseWorkspace): void { ); } catch (\Exception $exception) { // 4.E) In case of an exception, reopen the old content stream and remove the newly created - $contentRepository->handle( + $commandHandlingDependencies->handle( ReopenContentStream::create( $oldWorkspaceContentStreamId, $oldWorkspaceContentStreamIdState, ) )->block(); - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $command->newContentStreamId ))->block(); - $this->resetContentGraphCache($contentRepository); throw $exception; } // 5) If everything worked, to avoid dangling content streams, we need to remove the old content stream - $contentRepository->handle(RemoveContentStream::create( + $commandHandlingDependencies->handle(RemoveContentStream::create( $oldWorkspaceContentStreamId ))->block(); $streamName = WorkspaceEventStreamName::fromWorkspaceName($command->workspaceName)->getEventStreamName(); - $this->resetContentGraphCache($contentRepository); return new EventsToPublish( $streamName, Events::with( @@ -796,13 +790,13 @@ private function commandMatchesAtLeastOneNode( */ private function handleDiscardWorkspace( DiscardWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); $newContentStream = $command->newContentStreamId; - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $newContentStream, $baseWorkspace->currentContentStreamId, @@ -838,16 +832,16 @@ private function handleDiscardWorkspace( */ private function handleChangeBaseWorkspace( ChangeBaseWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); $this->requireEmptyWorkspace($workspace); - $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); - $baseWorkspace = $this->requireBaseWorkspace($workspace, $contentRepository->getWorkspaceFinder()); + $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); + $baseWorkspace = $this->requireBaseWorkspace($workspace, $commandHandlingDependencies->getWorkspaceFinder()); - $this->requireNonCircularRelationBetweenWorkspaces($workspace, $baseWorkspace, $contentRepository->getWorkspaceFinder()); + $this->requireNonCircularRelationBetweenWorkspaces($workspace, $baseWorkspace, $commandHandlingDependencies->getWorkspaceFinder()); - $contentRepository->handle( + $commandHandlingDependencies->handle( ForkContentStream::create( $command->newContentStreamId, $baseWorkspace->currentContentStreamId, @@ -863,7 +857,6 @@ private function handleChangeBaseWorkspace( ) ); - $this->resetContentGraphCache($contentRepository); return new EventsToPublish( $streamName, $events, @@ -876,11 +869,11 @@ private function handleChangeBaseWorkspace( */ private function handleDeleteWorkspace( DeleteWorkspace $command, - ContentRepository $contentRepository, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - $workspace = $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); + $workspace = $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); - $contentRepository->handle( + $commandHandlingDependencies->handle( RemoveContentStream::create( $workspace->currentContentStreamId ) @@ -905,9 +898,9 @@ private function handleDeleteWorkspace( */ private function handleChangeWorkspaceOwner( ChangeWorkspaceOwner $command, - ContentRepository $contentRepository + CommandHandlingDependencies $commandHandlingDependencies ): EventsToPublish { - $this->requireWorkspace($command->workspaceName, $contentRepository->getWorkspaceFinder()); + $this->requireWorkspace($command->workspaceName, $commandHandlingDependencies->getWorkspaceFinder()); $events = Events::with( new WorkspaceOwnerWasChanged( @@ -1014,9 +1007,4 @@ private function hasEventsInContentStreamExceptForking( return false; } - - private function resetContentGraphCache(ContentRepository $contentRepository): void - { - $contentRepository->projectionState(ContentGraphFinder::class)->forgetInstances(); - } } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjection.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjection.php index c5089577235..01de943a906 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjection.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphProjection.php @@ -19,10 +19,10 @@ final class ContentGraphProjection implements ProjectionInterface, WithMarkStaleInterface { /** - * @param WithMarkStaleInterface&ProjectionInterface $projectionImplementation + * @param ProjectionInterface $projectionImplementation */ public function __construct( - private readonly ProjectionInterface&WithMarkStaleInterface $projectionImplementation + private readonly ProjectionInterface $projectionImplementation ) { } @@ -63,6 +63,10 @@ public function getCheckpointStorage(): CheckpointStorageInterface public function markStale(): void { - $this->projectionImplementation->markStale(); + if ($this->projectionImplementation instanceof WithMarkStaleInterface) { + $this->projectionImplementation->markStale(); + } + + $this->getState()->forgetInstances(); } } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index 6da280c28b7..2bfe0fae891 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -283,7 +283,6 @@ protected function initializeCurrentNodeFromContentGraph(callable $query): void } else { $contentGraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName); } - // FIXME: query->workspaceName this _might_ be wrong as the query could use a different workspace/contentstream $this->currentNode = $query($contentGraph); } From f6d7cb19745396bf4f7320ed4693daf779f12c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Wed, 8 May 2024 15:36:40 +0200 Subject: [PATCH 126/214] Fix phpcs errors --- .../Classes/CommandHandlingDependencies.php | 1 + .../Classes/Feature/NodeModification/NodeModification.php | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php index da04fcf752f..249b20b3aa1 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php @@ -1,4 +1,5 @@ Date: Wed, 8 May 2024 15:55:58 +0200 Subject: [PATCH 127/214] Fix lint errors --- Neos.ContentRepository.Core/Classes/ContentRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index 859364ef96a..3c54a2735ed 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -83,7 +83,7 @@ public function __construct( private readonly UserIdProviderInterface $userIdProvider, private readonly ClockInterface $clock, ) { - $this->commandHandlerToBeNamed = new CommandHandlingDependencies($this); + $this->commandHandlingDependencies = new CommandHandlingDependencies($this); } /** @@ -100,7 +100,7 @@ public function handle(CommandInterface $command): CommandResult { // the commands only calculate which events they want to have published, but do not do the // publishing themselves - $eventsToPublish = $this->commandBus->handle($command, $this->commandHandlerToBeNamed); + $eventsToPublish = $this->commandBus->handle($command, $this->commandHandlingDependencies); // TODO meaningful exception message $initiatingUserId = $this->userIdProvider->getUserId(); From 7b884b5d8a45f50abeebf019881733bb34eebd83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCller?= Date: Thu, 9 May 2024 00:57:32 +0200 Subject: [PATCH 128/214] Apply suggestions from code review Co-authored-by: Bastian Waidelich --- .../Classes/Factory/ContentRepositoryFactory.php | 5 ++--- .../Factory/ContentRepositoryServiceFactoryDependencies.php | 6 +++--- .../Classes/Feature/ContentStreamCommandHandler.php | 6 +++--- .../Classes/Feature/NodeModification/NodeModification.php | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php index 3ad56ed1a49..bba7d784579 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php @@ -62,7 +62,6 @@ public function __construct( $contentDimensionSource, $contentDimensionZookeeper ); - $propertyConverter = new PropertyConverter($propertySerializer); $this->projectionFactoryDependencies = new ProjectionFactoryDependencies( $contentRepositoryId, $eventStore, @@ -101,7 +100,7 @@ public function getOrBuild(): ContentRepository $this->projectionFactoryDependencies->interDimensionalVariationGraph, $this->projectionFactoryDependencies->contentDimensionSource, $this->userIdProvider, - $this->clock + $this->clock, ); } return $this->contentRepository; @@ -126,7 +125,7 @@ public function buildService( $this->projectionFactoryDependencies, $this->getOrBuild(), $this->buildEventPersister(), - $this->projectionsAndCatchUpHooks->projections + $this->projectionsAndCatchUpHooks->projections, ); return $serviceFactory->build($serviceFactoryDependencies); } diff --git a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php index 9386f9bdb1d..ca9df198cee 100644 --- a/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php +++ b/Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryServiceFactoryDependencies.php @@ -46,7 +46,7 @@ private function __construct( public ContentRepository $contentRepository, // we don't need CommandBus, because this is included in ContentRepository->handle() public EventPersister $eventPersister, - public Projections $projections + public Projections $projections, ) { } @@ -57,7 +57,7 @@ public static function create( ProjectionFactoryDependencies $projectionFactoryDependencies, ContentRepository $contentRepository, EventPersister $eventPersister, - Projections $projections + Projections $projections, ): self { return new self( $projectionFactoryDependencies->contentRepositoryId, @@ -70,7 +70,7 @@ public static function create( $projectionFactoryDependencies->propertyConverter, $contentRepository, $eventPersister, - $projections + $projections, ); } } diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php index 3b3547fa9f9..882a700f313 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php @@ -84,7 +84,7 @@ private function handleCreateContentStream( private function handleCloseContentStream( CloseContentStream $command, - CommandHandlingDependencies $commandHandlingDependencies + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { $this->requireContentStreamToExist($command->contentStreamId, $commandHandlingDependencies); $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $commandHandlingDependencies); @@ -104,7 +104,7 @@ private function handleCloseContentStream( private function handleReopenContentStream( ReopenContentStream $command, - CommandHandlingDependencies $commandHandlingDependencies + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { $this->requireContentStreamToExist($command->contentStreamId, $commandHandlingDependencies); $expectedVersion = $this->getExpectedVersionOfContentStream($command->contentStreamId, $commandHandlingDependencies); @@ -134,7 +134,7 @@ private function handleForkContentStream( $this->requireContentStreamToExist($command->sourceContentStreamId, $commandHandlingDependencies); $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId, $commandHandlingDependencies); - // TOOD: THis is not great + // TODO: This is not great $sourceContentStreamVersion = $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($command->sourceContentStreamId); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->newContentStreamId) diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php index f316367a472..ec347b252fd 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeModification/NodeModification.php @@ -150,7 +150,7 @@ private function splitPropertiesToUnsetByScope(PropertyNames $propertiesToUnset, } return array_map( - static fn(array $propertyValues): PropertyNames => PropertyNames::fromArray($propertyValues), + PropertyNames::fromArray(...), $propertiesToUnsetByScope ); } From 0c47965a78f00937dc51c18bb53f44eb4ead3007 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 9 May 2024 09:44:10 +0200 Subject: [PATCH 129/214] TASK: Adjust comment in legacy migration --- .../Classes/NodeDataToEventsProcessor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php index cfe8e1a8dea..224a3d2ecf8 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php @@ -376,7 +376,7 @@ public function extractPropertyValuesAndReferences(array $nodeDataRow, NodeType } } - // hiddenInIndex is stored as separate column in the nodedata table, but we need it as (internal) property + // hiddenInIndex is stored as separate column in the nodedata table, but we need it as property if ($nodeDataRow['hiddeninindex']) { $properties['hiddenInMenu'] = true; } From aeaad5cecf22d7f7b99af33cf277d86c512148a2 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 9 May 2024 09:46:20 +0200 Subject: [PATCH 130/214] TASK: Reset `$this->currentWorkspaceName` in behat tests before scenario ... this seems to have been missed and will cause headache at some point. --- .../Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php index 832cfcef5fa..87071f5014d 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php @@ -106,6 +106,7 @@ public function beforeEventSourcedScenarioDispatcher(BeforeScenarioScope $scope) $this->currentDimensionSpacePoint = null; $this->currentRootNodeAggregateId = null; $this->currentContentStreamId = null; + $this->currentWorkspaceName = null; $this->currentNodeAggregate = null; $this->currentNode = null; } From 700481bfffcc308fb7331d6383780b451e6f71c6 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 9 May 2024 09:50:03 +0200 Subject: [PATCH 131/214] TASK: Remove legacy cr functional test artifacts --- .../Domain/Fixtures/TestNodePostprocessor.php | 36 -- .../Fixtures/TestObjectForSerialization.php | 47 --- .../Functional/Fixtures/NodeStructure.xml | 343 ------------------ .../NodesWithAndWithoutDimensions.xml | 31 -- .../Configuration/Testing/NodeTypes.yaml | 207 ----------- .../Configuration/Testing/Policy.yaml | 7 - 6 files changed, 671 deletions(-) delete mode 100644 Neos.ContentRepository.Core/Tests/Functional/Domain/Fixtures/TestNodePostprocessor.php delete mode 100644 Neos.ContentRepository.Core/Tests/Functional/Domain/Fixtures/TestObjectForSerialization.php delete mode 100644 Neos.ContentRepository.Core/Tests/Functional/Fixtures/NodeStructure.xml delete mode 100644 Neos.ContentRepository.Core/Tests/Functional/Fixtures/NodesWithAndWithoutDimensions.xml delete mode 100644 Neos.ContentRepository.NodeAccess/Configuration/Testing/NodeTypes.yaml delete mode 100644 Neos.ContentRepository.NodeAccess/Configuration/Testing/Policy.yaml diff --git a/Neos.ContentRepository.Core/Tests/Functional/Domain/Fixtures/TestNodePostprocessor.php b/Neos.ContentRepository.Core/Tests/Functional/Domain/Fixtures/TestNodePostprocessor.php deleted file mode 100644 index 4ed06672849..00000000000 --- a/Neos.ContentRepository.Core/Tests/Functional/Domain/Fixtures/TestNodePostprocessor.php +++ /dev/null @@ -1,36 +0,0 @@ -isOfType('Neos.ContentRepository.Testing:NodeTypeWithProcessor')) { - $someOption = isset($options['someOption']) ? $options['someOption'] : ''; - $someOtherOption = isset($options['someOtherOption']) ? $options['someOtherOption'] : ''; - $configuration['properties']['test1']['defaultValue'] = sprintf('The value of "someOption" is "%s", the value of "someOtherOption" is "%s"', $someOption, $someOtherOption); - } - } -} diff --git a/Neos.ContentRepository.Core/Tests/Functional/Domain/Fixtures/TestObjectForSerialization.php b/Neos.ContentRepository.Core/Tests/Functional/Domain/Fixtures/TestObjectForSerialization.php deleted file mode 100644 index ca8d3ef0fbb..00000000000 --- a/Neos.ContentRepository.Core/Tests/Functional/Domain/Fixtures/TestObjectForSerialization.php +++ /dev/null @@ -1,47 +0,0 @@ -value = $value; - } - - /** - * @return object - */ - public function getValue() - { - return $this->value; - } - - /** - * @return array - */ - public function __sleep() - { - return ['value']; - } -} diff --git a/Neos.ContentRepository.Core/Tests/Functional/Fixtures/NodeStructure.xml b/Neos.ContentRepository.Core/Tests/Functional/Fixtures/NodeStructure.xml deleted file mode 100644 index f2baf33b053..00000000000 --- a/Neos.ContentRepository.Core/Tests/Functional/Fixtures/NodeStructure.xml +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Neos.ContentRepository.Core/Tests/Functional/Fixtures/NodesWithAndWithoutDimensions.xml b/Neos.ContentRepository.Core/Tests/Functional/Fixtures/NodesWithAndWithoutDimensions.xml deleted file mode 100644 index cc2515be305..00000000000 --- a/Neos.ContentRepository.Core/Tests/Functional/Fixtures/NodesWithAndWithoutDimensions.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - diff --git a/Neos.ContentRepository.NodeAccess/Configuration/Testing/NodeTypes.yaml b/Neos.ContentRepository.NodeAccess/Configuration/Testing/NodeTypes.yaml deleted file mode 100644 index 2093f507f19..00000000000 --- a/Neos.ContentRepository.NodeAccess/Configuration/Testing/NodeTypes.yaml +++ /dev/null @@ -1,207 +0,0 @@ -# Node Types for Functional Tests - -'Neos.ContentRepository.Testing:NodeType': - properties: - test1: - defaultValue: 'default value 1' - test2: - defaultValue: 'default value 2' - constraints: - nodeTypes: - 'Neos.ContentRepository.Testing:NodeType': true - 'Neos.ContentRepository.Testing:NodeTypeWithSubnodes': true - -'Neos.ContentRepository.Testing:NodeTypeWithSubnodes': - childNodes: - subnode1: - type: 'Neos.ContentRepository.Testing:NodeType' - constraints: - nodeTypes: - 'Neos.ContentRepository.Testing:NodeType': true - 'Neos.ContentRepository.Testing:Document': true - -'Neos.ContentRepository.Testing:NodeTypeWithProcessor': - properties: - test1: - defaultValue: 'default value 1' - postprocessors: - 'processor1': - postprocessor: 'Neos\ContentRepository\Core\Tests\Functional\Domain\Fixtures\TestNodePostprocessor' - postprocessorOptions: - someOption: 'someOverriddenValue' - someOtherOption: 'someOtherValue' - 'processor2': - position: start - postprocessor: 'Neos\ContentRepository\Core\Tests\Functional\Domain\Fixtures\TestNodePostprocessor' - postprocessorOptions: - someOption: 'someValue' - - -'Neos.ContentRepository.Testing:NodeTypeWithReferences': - properties: - property1: - type: string - property2: - type: reference - property3: - type: references - -'Neos.ContentRepository.Testing:Headline': - superTypes: - 'Neos.ContentRepository.Testing:Content': true - properties: - title: - type: string - -'Neos.ContentRepository.Testing:NodeTypeWithEntities': - properties: - image: - type: 'Neos\Flow\Tests\Functional\Persistence\Fixtures\Image' - wrappedImage: - type: 'Neos\ContentRepository\Core\Tests\Functional\Domain\Fixtures\TestObjectForSerialization' - images: - type: 'array' - -'Neos.ContentRepository.Testing:HappyTestingNode': - class: 'Neos\ContentRepository\Core\Tests\Functional\Domain\Fixtures\HappyNode' - -# Constraint behat test nodetypes -'Neos.ContentRepository.Testing:Node': - abstract: true - -'Neos.ContentRepository.Testing:ContentCollection': - superTypes: - 'Neos.ContentRepository.Testing:Node': true - constraints: - nodeTypes: - 'Neos.ContentRepository.Testing:Headline': true - 'Neos.ContentRepository.Testing:TwoColumn': true - 'Neos.ContentRepository.Testing:ThreeColumn': true - 'Neos.ContentRepository.Testing:Text': true - 'Neos.ContentRepository.Testing:Html': true - 'Neos.ContentRepository.Testing:List': true - -'Neos.ContentRepository.Testing:Document': - aggregate: true - -'Neos.ContentRepository.Testing:Page': - superTypes: - 'Neos.ContentRepository.Testing:Document': true - properties: - title: - type: string - childNodes: - 'main': - type: 'Neos.ContentRepository.Testing:ContentCollection' - constraints: - nodeTypes: - '*': true - -'Neos.ContentRepository.Testing:Chapter': - superTypes: - 'Neos.ContentRepository.Testing:Page': true - -'Neos.ContentRepository.Testing:PageWithConfiguredLabel': - superTypes: - 'Neos.ContentRepository.Testing:Page': true - label: "${q(node).property('title') || q(node).property('text') || ((node.nodeType.label || node.nodeType.name) + ' (' + node.name + ')')}" - ui: - label: 'Labeled Page' - -'Neos.ContentRepository.Testing:Column': - superTypes: - 'Neos.ContentRepository.Testing:Content': true - 'Neos.ContentRepository.Testing:ContentMixin': false - abstract: true - -'Neos.ContentRepository.Testing:TwoColumn': - superTypes: - 'Neos.ContentRepository.Testing:Column': true - childNodes: - column0: - type: 'Neos.ContentRepository.Testing:ContentCollection' - column1: - type: 'Neos.ContentRepository.Testing:ContentCollection' - -'Neos.ContentRepository.Testing:ThreeColumn': - superTypes: - 'Neos.ContentRepository.Testing:Column': true - childNodes: - column0: - type: 'Neos.ContentRepository.Testing:ContentCollection' - column1: - type: 'Neos.ContentRepository.Testing:ContentCollection' - column2: - type: 'Neos.ContentRepository.Testing:ContentCollection' - -'Neos.ContentRepository.Testing:Content': - superTypes: - 'Neos.ContentRepository.Testing:ContentMixin': true - constraints: - nodeTypes: - '*': false - -'Neos.ContentRepository.Testing:ContentMixin': [] - -'Neos.ContentRepository.Testing:Text': - superTypes: - 'Neos.ContentRepository.Testing:Content': true - properties: - text: - type: string - -'Neos.ContentRepository.Testing:NodeTypeWithSubnodesAndConstraints': - childNodes: - subnode1: - type: 'Neos.ContentRepository.Testing:NodeType' - constraints: - nodeTypes: - 'Neos.ContentRepository.Testing:Headline': true # allowed - -'Neos.ContentRepository.Testing:Image': - superTypes: - 'Neos.ContentRepository.Testing:Content': true - -'Neos.ContentRepository.Testing:TextWithImage': - superTypes: - 'Neos.ContentRepository.Testing:Text': true - 'Neos.ContentRepository.Testing:Image': true - -'Neos.ContentRepository.Testing:Html': - superTypes: - 'Neos.ContentRepository.Testing:Content': true - properties: - source: - type: string - defaultValue: '

Enter HTML here

' - -'Neos.ContentRepository.Testing:List': - superTypes: - 'Neos.ContentRepository.Testing:Content': true - childNodes: - items: - type: 'Neos.ContentRepository.Testing:ContentCollection' - constraints: - nodeTypes: - 'Neos.ContentRepository.Testing:ListItem': true - '*': false - -'Neos.ContentRepository.Testing:ListItem': - superTypes: - 'Neos.ContentRepository.Testing:Content': true - properties: - text: - type: 'string' - -'Neos.ContentRepository.Testing:ImportExport': - properties: - description: - type: string - someDate: - type: DateTime - -'Neos.ContentRepository.Testing:NodeTypeWithPlainLabel': - label: 'Test nodetype' - -'Neos.ContentRepository.Testing:NodeTypeWithEelExpressionLabel': - label: '${"Test" + " " + "nodetype"}' diff --git a/Neos.ContentRepository.NodeAccess/Configuration/Testing/Policy.yaml b/Neos.ContentRepository.NodeAccess/Configuration/Testing/Policy.yaml deleted file mode 100644 index a04a7a5245e..00000000000 --- a/Neos.ContentRepository.NodeAccess/Configuration/Testing/Policy.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# # -# Security policy for the ContentRepository package # -# # - -roles: - 'Neos.ContentRepository:TestingAdministrator': - parentRoles: ['Neos.ContentRepository:Administrator'] From fe0063b7e4fbaea9b0a72abfd323354fabf42173 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 9 May 2024 09:51:47 +0200 Subject: [PATCH 132/214] TASK: Remove oddly placed obsolete behat configuration --- .../Configuration/Testing/Behat/Settings.yaml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 Neos.ContentRepository.NodeAccess/Configuration/Testing/Behat/Settings.yaml diff --git a/Neos.ContentRepository.NodeAccess/Configuration/Testing/Behat/Settings.yaml b/Neos.ContentRepository.NodeAccess/Configuration/Testing/Behat/Settings.yaml deleted file mode 100644 index f75fc0b2009..00000000000 --- a/Neos.ContentRepository.NodeAccess/Configuration/Testing/Behat/Settings.yaml +++ /dev/null @@ -1,5 +0,0 @@ -Neos: - Flow: - persistence: - backendOptions: - driver: pdo_mysql From d3f13a4d3e8bfde1f1d3843424c5fb05f8eaf827 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Thu, 9 May 2024 08:05:23 +0000 Subject: [PATCH 133/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index 21e629f6c3f..191035eaea2 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-05-06 +The following reference was automatically generated from code on 2024-05-09 .. _`Neos Command Reference: NEOS.FLOW`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index 7d62820bfc2..53f872ce65f 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-05-06 +This reference was automatically generated from code on 2024-05-09 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index 20bdc8a7e87..913cb604ac0 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-05-06 +This reference was automatically generated from code on 2024-05-09 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index 3926453820e..dd645be4d8a 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-05-06 +This reference was automatically generated from code on 2024-05-09 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index d3ff40b5705..194f066df7a 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-05-06 +This reference was automatically generated from code on 2024-05-09 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index 3ced4268b34..8112dce9e69 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-05-06 +This reference was automatically generated from code on 2024-05-09 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From 89f13339a6d47d5b0a0bb24cf482576ac6de70a4 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 9 May 2024 10:12:28 +0200 Subject: [PATCH 134/214] TASK: Remove obsolete not referenced things --- .../Tests/Functional/AbstractNodeTest.php | 86 ------------------- .../Domain/Service/Fixtures/Sites.xml | 51 ----------- 2 files changed, 137 deletions(-) delete mode 100644 Neos.Neos/Tests/Functional/AbstractNodeTest.php delete mode 100644 Neos.Neos/Tests/Functional/Domain/Service/Fixtures/Sites.xml diff --git a/Neos.Neos/Tests/Functional/AbstractNodeTest.php b/Neos.Neos/Tests/Functional/AbstractNodeTest.php deleted file mode 100644 index b41c7069dff..00000000000 --- a/Neos.Neos/Tests/Functional/AbstractNodeTest.php +++ /dev/null @@ -1,86 +0,0 @@ -contextFactory = $this->objectManager->get(ContextFactoryInterface::class); - $contentContext = $this->contextFactory->create(['workspaceName' => 'live']); - $siteImportService = $this->objectManager->get(SiteImportService::class); - $siteImportService->importFromFile(__DIR__ . '/' . $this->fixtureFileName, $contentContext); - $this->persistenceManager->persistAll(); - - if ($this->nodeContextPath !== null) { - $this->node = $this->getNodeWithContextPath($this->nodeContextPath); - } - } - - /** - * Retrieve a node through the property mapper - * - * @param $contextPath - * @return Node - */ - protected function getNodeWithContextPath($contextPath) - { - /* @var $propertyMapper \Neos\Flow\Property\PropertyMapper */ - $propertyMapper = $this->objectManager->get(PropertyMapper::class); - $node = $propertyMapper->convert($contextPath, Node::class); - self::assertFalse($propertyMapper->getMessages()->hasErrors(), 'There were errors converting ' . $contextPath); - return $node; - } - - public function tearDown(): void - { - parent::tearDown(); - - $this->inject($this->contextFactory, 'contextInstances', []); - $this->inject($this->objectManager->get(AssetInterfaceConverter::class), 'resourcesAlreadyConvertedToAssets', []); - } -} diff --git a/Neos.Neos/Tests/Functional/Domain/Service/Fixtures/Sites.xml b/Neos.Neos/Tests/Functional/Domain/Service/Fixtures/Sites.xml deleted file mode 100644 index 1fe73b0d4cd..00000000000 --- a/Neos.Neos/Tests/Functional/Domain/Service/Fixtures/Sites.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - From 4de73cdaface97c0188c883f53c36f7b79d32618 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 9 May 2024 10:13:11 +0200 Subject: [PATCH 135/214] TASK: Remove content cache flusher test it will be fully migrated via https://github.com/neos/neos-development-collection/issues/5037 --- .../Fusion/Cache/ContentCacheFlusherTest.php | 260 ------------------ .../Fusion/Cache/Fixtures/CacheableNodes.xml | 46 ---- 2 files changed, 306 deletions(-) delete mode 100644 Neos.Neos/Tests/Functional/Fusion/Cache/ContentCacheFlusherTest.php delete mode 100644 Neos.Neos/Tests/Functional/Fusion/Cache/Fixtures/CacheableNodes.xml diff --git a/Neos.Neos/Tests/Functional/Fusion/Cache/ContentCacheFlusherTest.php b/Neos.Neos/Tests/Functional/Fusion/Cache/ContentCacheFlusherTest.php deleted file mode 100644 index 72ae26d3aba..00000000000 --- a/Neos.Neos/Tests/Functional/Fusion/Cache/ContentCacheFlusherTest.php +++ /dev/null @@ -1,260 +0,0 @@ -markTestSkipped('re-implement with Neos 9.0'); - parent::setUp(); - - $this->workspaceRepository = $this->objectManager->get(WorkspaceRepository::class); - $this->nodeDataRepository = $this->objectManager->get(NodeDataRepository::class); - $this->contextFactory = $this->objectManager->get(ContextFactoryInterface::class); - $this->contentCacheFlusher = $this->objectManager->get(ContentCacheFlusher::class); - - $this->context = $this->contextFactory->create(['workspaceName' => 'live']); - $siteImportService = $this->objectManager->get(\Neos\Neos\Domain\Service\SiteImportService::class); - $siteImportService->importFromFile(__DIR__ . '/Fixtures/CacheableNodes.xml', $this->context); - - // Assume an empty state for $contentCacheFlusher - this is needed as importing nodes will register - // changes to the ContentCacheFlusher - $this->inject($this->contentCacheFlusher, 'workspacesToFlush', []); - $this->inject($this->contentCacheFlusher, 'tagsToFlush', []); - - $this->persistenceManager->persistAll(); - $this->persistenceManager->clearState(); - } - - /** - * @test - */ - public function flushingANodeWillResolveAllWorkspacesToFlush() - { - // Add more workspaces - $workspaceFirstLevel = new Workspace('first-level'); - $workspaceSecondLevel = new Workspace('second-level'); - $workspaceAlsoOnSecondLevel = new Workspace('also-second-level'); - $workspaceThirdLevel = new Workspace('third-level'); - - // And build up a chain - $liveWorkspace = $this->workspaceRepository->findByIdentifier('live'); - $workspaceThirdLevel->setBaseWorkspace($workspaceSecondLevel); - $workspaceSecondLevel->setBaseWorkspace($workspaceFirstLevel); - $workspaceAlsoOnSecondLevel->setBaseWorkspace($workspaceFirstLevel); - $workspaceFirstLevel->setBaseWorkspace($liveWorkspace); - - $this->workspaceRepository->add($workspaceFirstLevel); - $this->workspaceRepository->add($workspaceSecondLevel); - $this->workspaceRepository->add($workspaceAlsoOnSecondLevel); - $this->workspaceRepository->add($workspaceThirdLevel); - - $this->persistenceManager->persistAll(); - $this->persistenceManager->clearState(); - - // Make sure that we do have multiple workspaces set up in our database - self::assertEquals(5, $this->workspaceRepository->countAll()); - - // Create/Fetch a node in workspace "first-level" - $fistLevelContext = $this->contextFactory->create(['workspaceName' => $workspaceFirstLevel->getName()]); - $nodeInFirstLevelWorkspace = $fistLevelContext->getRootNode(); - - // When the node is flushed we expect three workspaces to be flushed - $this->contentCacheFlusher->registerNodeChange($nodeInFirstLevelWorkspace); - - $workspacesToFlush = ObjectAccess::getProperty($this->contentCacheFlusher, 'workspacesToFlush', true); - $workspaceChain = $workspacesToFlush['first-level']; - - self::assertArrayNotHasKey('live', $workspaceChain); - self::assertArrayHasKey('first-level', $workspaceChain); - self::assertArrayHasKey('second-level', $workspaceChain); - self::assertArrayHasKey('also-second-level', $workspaceChain); - self::assertArrayHasKey('third-level', $workspaceChain); - } - - /** - * @test - */ - public function flushingANodeWithAnAdditionalTargetWorkspaceWillAlsoResolveThatWorkspace() - { - // Add more workspaces - $workspaceFirstLevel = new Workspace('first-level'); - $workspaceSecondLevel = new Workspace('second-level'); - $workspaceAlsoOnSecondLevel = new Workspace('also-second-level'); - $workspaceThirdLevel = new Workspace('third-level'); - $workspaceAlsoFirstLevel = new Workspace('also-first-level'); - - // And build up a chain - $liveWorkspace = $this->workspaceRepository->findByIdentifier('live'); - $workspaceThirdLevel->setBaseWorkspace($workspaceSecondLevel); - $workspaceSecondLevel->setBaseWorkspace($workspaceFirstLevel); - $workspaceAlsoOnSecondLevel->setBaseWorkspace($workspaceFirstLevel); - $workspaceFirstLevel->setBaseWorkspace($liveWorkspace); - $workspaceAlsoFirstLevel->setBaseWorkspace($liveWorkspace); - - $this->workspaceRepository->add($workspaceFirstLevel); - $this->workspaceRepository->add($workspaceSecondLevel); - $this->workspaceRepository->add($workspaceAlsoOnSecondLevel); - $this->workspaceRepository->add($workspaceThirdLevel); - $this->workspaceRepository->add($workspaceAlsoFirstLevel); - - $this->persistenceManager->persistAll(); - $this->persistenceManager->clearState(); - - // Make sure that we do have multiple workspaces set up in our database - self::assertEquals(6, $this->workspaceRepository->countAll()); - - // Create/Fetch a node in workspace "first-level" - $fistLevelContext = $this->contextFactory->create(['workspaceName' => $workspaceFirstLevel->getName()]); - $nodeInFirstLevelWorkspace = $fistLevelContext->getRootNode(); - - // When the node is flushed we expect three workspaces to be flushed - $this->contentCacheFlusher->registerNodeChange($nodeInFirstLevelWorkspace, $workspaceAlsoFirstLevel); - - $workspacesToFlush = ObjectAccess::getProperty($this->contentCacheFlusher, 'workspacesToFlush', true); - - self::assertArrayHasKey('also-first-level', $workspacesToFlush); - self::assertArrayHasKey('first-level', $workspacesToFlush); - } - - /** - * @test - */ - public function aNodeChangeWillRegisterNodeIdentifierTagsForAllWorkspaces() - { - $workspaceFirstLevel = new Workspace('first-level'); - - $liveWorkspace = $this->workspaceRepository->findByIdentifier('live'); - $workspaceFirstLevel->setBaseWorkspace($liveWorkspace); - $this->workspaceRepository->add($workspaceFirstLevel); - - $this->persistenceManager->persistAll(); - $this->persistenceManager->clearState(); - - $nodeIdentifier = 'c381f64d-4269-429a-9c21-6d846115addd'; - $nodeToFlush = $this->context->getNodeByIdentifier($nodeIdentifier); - - $this->contentCacheFlusher->registerNodeChange($nodeToFlush); - - $tagsToFlush = ObjectAccess::getProperty($this->contentCacheFlusher, 'tagsToFlush', true); - - $cachingHelper = new CachingHelper(); - - $workspacesToTest = []; - $workspacesToTest[$liveWorkspace->getName()] = $cachingHelper->renderWorkspaceTagForContextNode($liveWorkspace->getName()); - $workspacesToTest[$workspaceFirstLevel->getName()] = $cachingHelper->renderWorkspaceTagForContextNode($workspaceFirstLevel->getName()); - - foreach ($workspacesToTest as $name => $workspaceHash) { - self::assertArrayHasKey('Node_'.$workspaceHash.'_'.$nodeIdentifier, $tagsToFlush, 'on workspace ' . $name); - self::assertArrayHasKey('DescendantOf_'.$workspaceHash.'_'.$nodeIdentifier, $tagsToFlush, 'on workspace ' . $name); - } - } - - /** - * @test - */ - public function aNodeChangeWillRegisterNodeTypeTagsForAllWorkspaces() - { - $workspaceFirstLevel = new Workspace('first-level'); - - $liveWorkspace = $this->workspaceRepository->findByIdentifier('live'); - $workspaceFirstLevel->setBaseWorkspace($liveWorkspace); - $this->workspaceRepository->add($workspaceFirstLevel); - - $this->persistenceManager->persistAll(); - $this->persistenceManager->clearState(); - - $nodeIdentifier = 'c381f64d-4269-429a-9c21-6d846115addd'; - $nodeToFlush = $this->context->getNodeByIdentifier($nodeIdentifier); - - $this->contentCacheFlusher->registerNodeChange($nodeToFlush); - - $tagsToFlush = ObjectAccess::getProperty($this->contentCacheFlusher, 'tagsToFlush', true); - - $cachingHelper = new CachingHelper(); - - $workspacesToTest = []; - $workspacesToTest[$liveWorkspace->getName()] = $cachingHelper->renderWorkspaceTagForContextNode($liveWorkspace->getName()); - $workspacesToTest[$workspaceFirstLevel->getName()] = $cachingHelper->renderWorkspaceTagForContextNode($workspaceFirstLevel->getName()); - - // Check for tags that respect the workspace hash - foreach ($workspacesToTest as $name => $workspaceHash) { - self::assertArrayHasKey('NodeType_'.$workspaceHash.'_Neos.Neos:Content', $tagsToFlush, 'on workspace ' . $name); - self::assertArrayHasKey('NodeType_'.$workspaceHash.'_Neos.Neos:Node', $tagsToFlush, 'on workspace ' . $name); - self::assertArrayHasKey('NodeType_'.$workspaceHash.'_Acme.Demo:Text', $tagsToFlush, 'on workspace ' . $name); - } - } - - /** - * @test - */ - public function aNodeChangeWillRegisterAllDescendantOfTagsForAllWorkspaces() - { - $workspaceFirstLevel = new Workspace('first-level'); - - $liveWorkspace = $this->workspaceRepository->findByIdentifier('live'); - $workspaceFirstLevel->setBaseWorkspace($liveWorkspace); - $this->workspaceRepository->add($workspaceFirstLevel); - - $this->persistenceManager->persistAll(); - $this->persistenceManager->clearState(); - - $nodeIdentifier = 'c381f64d-4269-429a-9c21-6d846115addd'; - $nodeToFlush = $this->context->getNodeByIdentifier($nodeIdentifier); - - $this->contentCacheFlusher->registerNodeChange($nodeToFlush); - - $tagsToFlush = ObjectAccess::getProperty($this->contentCacheFlusher, 'tagsToFlush', true); - - $cachingHelper = new CachingHelper(); - - $workspacesToTest = []; - $workspacesToTest[$liveWorkspace->getName()] = $cachingHelper->renderWorkspaceTagForContextNode($liveWorkspace->getName()); - $workspacesToTest[$workspaceFirstLevel->getName()] = $cachingHelper->renderWorkspaceTagForContextNode($workspaceFirstLevel->getName()); - - foreach ($workspacesToTest as $name => $workspaceHash) { - self::assertArrayHasKey('DescendantOf_'.$workspaceHash.'_c381f64d-4269-429a-9c21-6d846115addd', $tagsToFlush, 'on workspace ' . $name); - self::assertArrayHasKey('DescendantOf_'.$workspaceHash.'_c381f64d-4269-429a-9c21-6d846115adde', $tagsToFlush, 'on workspace ' . $name); - self::assertArrayHasKey('DescendantOf_'.$workspaceHash.'_c381f64d-4269-429a-9c21-6d846115addf', $tagsToFlush, 'on workspace ' . $name); - } - } - - public function tearDown(): void - { - $this->contextFactory->reset(); - parent::tearDown(); - } -} diff --git a/Neos.Neos/Tests/Functional/Fusion/Cache/Fixtures/CacheableNodes.xml b/Neos.Neos/Tests/Functional/Fusion/Cache/Fixtures/CacheableNodes.xml deleted file mode 100644 index d76d395d8d2..00000000000 --- a/Neos.Neos/Tests/Functional/Fusion/Cache/Fixtures/CacheableNodes.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - From b920e61ac52da4c2e80f0005e49ca2653a1b6a44 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 9 May 2024 10:13:48 +0200 Subject: [PATCH 136/214] TASK: Remove flow query sort operation test it will be fully migrated via https://github.com/neos/neos-development-collection/pull/5015#issuecomment-2102168505 --- .../Fixtures/SortableNodes.xml | 86 ------ .../FlowQueryOperations/SortOperationTest.php | 244 ------------------ 2 files changed, 330 deletions(-) delete mode 100644 Neos.Neos/Tests/Functional/FlowQueryOperations/Fixtures/SortableNodes.xml delete mode 100755 Neos.Neos/Tests/Functional/FlowQueryOperations/SortOperationTest.php diff --git a/Neos.Neos/Tests/Functional/FlowQueryOperations/Fixtures/SortableNodes.xml b/Neos.Neos/Tests/Functional/FlowQueryOperations/Fixtures/SortableNodes.xml deleted file mode 100644 index 125b5045ecb..00000000000 --- a/Neos.Neos/Tests/Functional/FlowQueryOperations/Fixtures/SortableNodes.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Neos.Neos/Tests/Functional/FlowQueryOperations/SortOperationTest.php b/Neos.Neos/Tests/Functional/FlowQueryOperations/SortOperationTest.php deleted file mode 100755 index 99fea63ef1a..00000000000 --- a/Neos.Neos/Tests/Functional/FlowQueryOperations/SortOperationTest.php +++ /dev/null @@ -1,244 +0,0 @@ -markTestSkipped('Needs to be rewritten with ES CR'); - parent::setUp(); - $workspaceRepository = $this->objectManager->get(\Neos\ContentRepository\Domain\Repository\WorkspaceRepository::class); - $workspaceRepository->add(new Workspace('live')); - $this->persistenceManager->persistAll(); - $this->contextFactory = $this->objectManager->get(\Neos\ContentRepository\Domain\Service\ContextFactoryInterface::class); - $this->context = $this->contextFactory->create(['workspaceName' => 'live']); - - - $siteImportService = $this->objectManager->get(\Neos\Neos\Domain\Service\SiteImportService::class); - $siteImportService->importFromFile(__DIR__ . '/Fixtures/SortableNodes.xml', $this->context); - $this->persistenceManager->persistAll(); - $this->persistenceManager->clearState(); - $this->inject($this->contextFactory, 'contextInstances', []); - - // The context is not important here, just a quick way to get a (live) workspace - // $context = $this->contextFactory->create(); - $this->nodeDataRepository = $this->objectManager->get(\Neos\ContentRepository\Domain\Repository\NodeDataRepository::class); - } - - /** - * @return void - */ - public function tearDown(): void - { - parent::tearDown(); - $this->inject($this->contextFactory, 'contextInstances', []); - } - - /** - * @test+ - */ - public function callWithoutArgumentsCausesException() - { - $this->expectException(FlowQueryException::class); - $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery([]); - $operation = new SortOperation(); - $operation->evaluate($flowQuery, []); - } - - /** - * @test - */ - public function invalidSortDirectionCausesException() - { - $this->expectException(FlowQueryException::class); - $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery([]); - $operation = new SortOperation(); - $operation->evaluate($flowQuery, ['title', 'FOO']); - } - - /** - * @test - */ - public function sortByStringAscending() - { - $nodesToSort = [ - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addd', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115adde', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addf', $this->context->getWorkspace(true), []) - ]; - $correctOrder = [ - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addf', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addd', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115adde', $this->context->getWorkspace(true), []) - ]; - $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery($nodesToSort); - $operation = new SortOperation(); - $operation->evaluate($flowQuery, ['title', 'ASC']); - - self::assertEquals($correctOrder, $flowQuery->getContext()); - } - - /** - * @test - */ - public function sortByStringDescending() - { - $nodesToSort = [ - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addd', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115adde', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addf', $this->context->getWorkspace(true), []) - ]; - $correctOrder = [ - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115adde', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addd', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addf', $this->context->getWorkspace(true), []) - ]; - $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery($nodesToSort); - $operation = new SortOperation(); - $operation->evaluate($flowQuery, ['title', 'DESC']); - - self::assertEquals($correctOrder, $flowQuery->getContext()); - } - - /** - * @test - */ - public function sortByDateTimeAscending() - { - $nodesToSort = [ - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addd', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115adde', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addf', $this->context->getWorkspace(true), []) - ]; - $correctOrder = [ - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115adde', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addf', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addd', $this->context->getWorkspace(true), []) - ]; - $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery($nodesToSort); - $operation = new SortOperation(); - $operation->evaluate($flowQuery, ['_lastPublicationDateTime', 'ASC']); - - self::assertEquals($correctOrder, $flowQuery->getContext()); - } - - /** - * @test - */ - public function sortByDateTimeDescending() - { - $nodesToSort = [ - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addd', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115adde', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addf', $this->context->getWorkspace(true), []) - ]; - $correctOrder = [ - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addd', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addf', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115adde', $this->context->getWorkspace(true), []) - ]; - $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery($nodesToSort); - $operation = new SortOperation(); - $operation->evaluate($flowQuery, ['_lastPublicationDateTime', 'DESC']); - - self::assertEquals($correctOrder, $flowQuery->getContext()); - } - - /** - * @test - */ - public function invalidSortOptionCausesException() - { - $this->expectException(FlowQueryException::class); - $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery([]); - $operation = new SortOperation(); - $operation->evaluate($flowQuery, ['title', 'ASC', 'SORT_BAR']); - } - - /** - * @test - */ - public function sortByStringNaturalCaseAscending() - { - $nodesToSort = [ - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addb', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addc', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addd', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115adde', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addf', $this->context->getWorkspace(true), []) - ]; - $correctOrder = [ - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addc', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addf', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addb', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addd', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115adde', $this->context->getWorkspace(true), []) - ]; - $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery($nodesToSort); - $operation = new SortOperation(); - $operation->evaluate($flowQuery, ['title', 'ASC', ['SORT_NATURAL', 'SORT_FLAG_CASE']]); - - self::assertEquals($correctOrder, $flowQuery->getContext()); - } - - /** - * @test - */ - public function sortByNumericDescending() - { - $nodesToSort = [ - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addc', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addb', $this->context->getWorkspace(true), []), - ]; - $correctOrder = [ - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addb', $this->context->getWorkspace(true), []), - $this->nodeDataRepository->findOneByIdentifier('c381f64d-4269-429a-9c21-6d846115addc', $this->context->getWorkspace(true), []), - ]; - $flowQuery = new \Neos\Eel\FlowQuery\FlowQuery($nodesToSort); - $operation = new SortOperation(); - $operation->evaluate($flowQuery, ['text', 'DESC', 'SORT_NUMERIC']); - - self::assertEquals($correctOrder, $flowQuery->getContext()); - } -} From 1ff403c6436d3e64bf91159830943667c2eadfac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Thu, 9 May 2024 11:16:41 +0200 Subject: [PATCH 137/214] Some cosmetic fixes --- .../Classes/CommandHandlingDependencies.php | 9 +++++++-- .../Projection/ContentGraph/ContentGraphInterface.php | 6 ++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php index 249b20b3aa1..42f23458d33 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php @@ -1,5 +1,7 @@ value to ContentGraphInterface diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 257361e960e..5ecb1bd025d 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -46,14 +46,14 @@ public function getSubgraph( ): ContentSubgraphInterface; /** - * @throws RootNodeAggregateDoesNotExist - * @api * Throws exception if no root aggregate found, because a Content Repository needs at least * one root node to function. * * Also throws exceptions if multiple root node aggregates of the given $nodeTypeName were found, * as this would lead to nondeterministic results in your code. * + * @throws RootNodeAggregateDoesNotExist + * @api */ public function findRootNodeAggregateByType( NodeTypeName $nodeTypeName @@ -145,6 +145,8 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( ): DimensionSpacePointSet; /** + * Provides the total number of projected nodes regardless of workspace or content stream. + * * @internal only for consumption in testcases */ public function countNodes(): int; From c9a3d1abbdee16a048b503529bc67a722a207653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Thu, 9 May 2024 11:47:52 +0200 Subject: [PATCH 138/214] Adjust documentation --- Neos.Neos/Documentation/References/NeosFusionReference.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.Neos/Documentation/References/NeosFusionReference.rst b/Neos.Neos/Documentation/References/NeosFusionReference.rst index 1e7967244ac..ecaa0e08f6f 100644 --- a/Neos.Neos/Documentation/References/NeosFusionReference.rst +++ b/Neos.Neos/Documentation/References/NeosFusionReference.rst @@ -1107,7 +1107,7 @@ If no node variant exists for the preset combination, a ``NULL`` node will be in :dimension: (optional, string): name of the dimension which this menu should be based on. Example: "language". :presets: (optional, array): If set, the presets rendered will be taken from this list of preset identifiers :includeAllPresets: (boolean, default **false**) If TRUE, include all presets, not only allowed combinations -:renderHiddenInMenu: (boolean, default **true**) If TRUE, render nodes which are marked as "hidded-in-index" +:renderHiddenInMenu: (boolean, default **true**) If TRUE, render nodes which are marked as "hidded-in-menu" :calculateItemStates: (boolean) activate the *expensive* calculation of item states defaults to ``false`` Each ``item`` has the following properties: From 67516e642e605f475bf07413b89be2e34dfc431f Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 9 May 2024 20:09:39 +0200 Subject: [PATCH 139/214] TASK: Declare `ContentGraphFinder` as internal and pass whole CR around instead --- .../Classes/ContentGraphFinder.php | 8 +++----- .../AssetUsage/Service/AssetUsageSyncService.php | 11 ++++------- .../Service/AssetUsageSyncServiceFactory.php | 4 +--- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php b/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php index 38d00320864..04bd5fac1de 100644 --- a/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php +++ b/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php @@ -22,12 +22,11 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** - * A finder for ContentGraphInterface bound to contentStream/Workspace + * A finder for a ContentGraph bound to contentStream / workspaceName * - * Userland code should not use this directly. You should get a ContentGraph - * via ContentRepository::getContentGraph() + * The API way of accessing a ContentGraph is via ContentRepository::getContentGraph() * - * @api Not for userland code, only for read access during write operations and in services + * @internal User land code should not use this directly. * @see ContentRepository::getContentGraph() */ final class ContentGraphFinder implements ProjectionStateInterface @@ -48,7 +47,6 @@ public function __construct( * * @throws WorkspaceDoesNotExist if there is no workspace with the provided name * @throws ContentStreamDoesNotExistYet if the provided workspace does not resolve to an existing content stream - * @api * @see ContentRepository::getContentGraph() */ public function getByWorkspaceName(WorkspaceName $workspaceName): ContentGraphInterface diff --git a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php index bf965135951..f73bf9865d5 100644 --- a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php +++ b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncService.php @@ -4,11 +4,9 @@ namespace Neos\Neos\AssetUsage\Service; -use Neos\ContentRepository\Core\ContentGraphFinder; +use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; -use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Repository\AssetRepository; use Neos\Neos\AssetUsage\Dto\AssetUsage; @@ -28,9 +26,8 @@ class AssetUsageSyncService implements ContentRepositoryServiceInterface private array $existingAssetsById = []; public function __construct( + private readonly ContentRepository $contentRepository, private readonly AssetUsageFinder $assetUsageFinder, - private readonly ContentGraphFinder $contentGraphFinder, - private readonly WorkspaceFinder $workspaceFinder, private readonly AssetRepository $assetRepository, private readonly AssetUsageRepository $assetUsageRepository, ) { @@ -59,11 +56,11 @@ public function isAssetUsageStillValid(AssetUsage $usage): bool $dimensionSpacePoint = $usage->originDimensionSpacePoint->toDimensionSpacePoint(); // FIXME: AssetUsage->workspaceName ? - $workspace = $this->workspaceFinder->findOneByCurrentContentStreamId($usage->contentStreamId); + $workspace = $this->contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->contentStreamId); if (is_null($workspace)) { return false; } - $subGraph = $this->contentGraphFinder->getByWorkspaceName($workspace->workspaceName) ->getSubgraph( + $subGraph = $this->contentRepository->getContentGraph($workspace->workspaceName)->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncServiceFactory.php b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncServiceFactory.php index 67339cd88ba..2e798ecf25d 100644 --- a/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncServiceFactory.php +++ b/Neos.Neos/Classes/AssetUsage/Service/AssetUsageSyncServiceFactory.php @@ -4,7 +4,6 @@ namespace Neos\Neos\AssetUsage\Service; -use Neos\ContentRepository\Core\ContentGraphFinder; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\Media\Domain\Repository\AssetRepository; @@ -27,9 +26,8 @@ public function build( ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies, ): AssetUsageSyncService { return new AssetUsageSyncService( + $serviceFactoryDependencies->contentRepository, $serviceFactoryDependencies->contentRepository->projectionState(AssetUsageFinder::class), - $serviceFactoryDependencies->contentRepository->projectionState(ContentGraphFinder::class), - $serviceFactoryDependencies->contentRepository->getWorkspaceFinder(), $this->assetRepository, $this->assetUsageRepositoryFactory->build($serviceFactoryDependencies->contentRepositoryId), ); From f639bf5fbac1b46729ac2c49b48ecb4b9c54c5c9 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 9 May 2024 20:29:40 +0200 Subject: [PATCH 140/214] TASK: Try to pass one `ContentGraphTableNames` instance along instead of magic strings in some places we still use the old and new way. --- ...rojectionIntegrityViolationDetectionTrait.php | 6 +++--- .../src/ContentGraphFactory.php | 4 ++-- .../src/ContentGraphTableNames.php | 10 +++++++++- .../src/DoctrineDbalContentGraphProjection.php | 9 +++++---- ...DoctrineDbalContentGraphProjectionFactory.php | 16 +++++----------- .../DoctrineDbalContentGraphSchemaBuilder.php | 5 +---- ...nIntegrityViolationDetectionRunnerFactory.php | 4 ++-- .../src/Domain/Projection/HierarchyRelation.php | 3 ++- .../src/Domain/Projection/NodeRecord.php | 3 ++- .../src/Domain/Repository/ContentGraph.php | 7 ++++--- .../src/Domain/Repository/ContentSubgraph.php | 5 +++-- .../DimensionSpacePointsRepository.php | 5 +---- .../src/NodeQueryBuilder.php | 5 +---- .../Features/Bootstrap/CrImportExportTrait.php | 8 -------- 14 files changed, 40 insertions(+), 50 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php index cb43d749e47..26f4a6651f1 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php @@ -17,7 +17,7 @@ use Behat\Gherkin\Node\TableNode; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Exception\InvalidArgumentException; -use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory; use Neos\ContentGraph\DoctrineDbalAdapter\Tests\Behavior\Features\Bootstrap\Helpers\TestingNodeAggregateId; @@ -54,9 +54,9 @@ abstract private function getObject(string $className): object; protected function getTableNamePrefix(): string { - return DoctrineDbalContentGraphProjectionFactory::graphProjectionTableNamePrefix( + return ContentGraphTableNames::create( $this->currentContentRepository->id - ); + )->tableNamePrefix; } public function setupDbalGraphAdapterIntegrityViolationTrait() diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php index 81c7e2d30a5..de24f71f1aa 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php @@ -33,7 +33,7 @@ public function __construct( private NodeFactory $nodeFactory, private ContentRepositoryId $contentRepositoryId, private NodeTypeManager $nodeTypeManager, - private string $tableNamePrefix + private ContentGraphTableNames $tableNames ) { } @@ -66,6 +66,6 @@ public function buildForWorkspace(WorkspaceName $workspaceName): ContentGraph public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraph { - return new ContentGraph($this->client, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNamePrefix, $workspaceName, $contentStreamId); + return new ContentGraph($this->client, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $contentStreamId); } } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php index 6d0611886cf..df37950d589 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -2,16 +2,24 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter; +use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; + /** * Encapsulates table name generation for content graph tables * @internal */ final readonly class ContentGraphTableNames { - private function __construct(private string $tableNamePrefix) + private function __construct(public string $tableNamePrefix) + { + } + + public static function create(ContentRepositoryId $contentRepositoryId): self { + return new self(sprintf('cr_%s_p_graph', $contentRepositoryId->value)); } + /** @deprecated */ public static function withPrefix(string $tableNamePrefix): self { return new self($tableNamePrefix); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index b57799b16e7..7a17423957d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -75,16 +75,17 @@ final class DoctrineDbalContentGraphProjection implements ProjectionInterface private DbalCheckpointStorage $checkpointStorage; - private ContentGraphTableNames $contentGraphTableNames; + /** @deprecated */ + private readonly string $tableNamePrefix; public function __construct( private readonly DbalClientInterface $dbalClient, private readonly ProjectionContentGraph $projectionContentGraph, - private readonly string $tableNamePrefix, + private readonly ContentGraphTableNames $contentGraphTableNames, private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository, private readonly ContentGraphFinder $contentGraphFinder ) { - $this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); + $this->tableNamePrefix = $this->contentGraphTableNames->tableNamePrefix; $this->checkpointStorage = new DbalCheckpointStorage( $this->dbalClient->getConnection(), $this->contentGraphTableNames->checkpoint(), @@ -120,7 +121,7 @@ private function determineRequiredSqlStatements(): array if (!$schemaManager instanceof AbstractSchemaManager) { throw new \RuntimeException('Failed to retrieve Schema Manager', 1625653914); } - $schema = (new DoctrineDbalContentGraphSchemaBuilder($this->tableNamePrefix))->buildSchema($schemaManager); + $schema = (new DoctrineDbalContentGraphSchemaBuilder($this->contentGraphTableNames))->buildSchema($schemaManager); return DbalSchemaDiff::determineRequiredSqlStatements($connection, $schema); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php index 4d24c49a34a..3ddb6b7a455 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php @@ -28,21 +28,15 @@ public function __construct( ) { } - public static function graphProjectionTableNamePrefix( - ContentRepositoryId $contentRepositoryId - ): string { - return sprintf('cr_%s_p_graph', $contentRepositoryId->value); - } - public function build( ProjectionFactoryDependencies $projectionFactoryDependencies, array $options, ): ContentGraphProjection { - $tableNamePrefix = self::graphProjectionTableNamePrefix( + $tableNames = ContentGraphTableNames::create( $projectionFactoryDependencies->contentRepositoryId ); - $dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbalClient->getConnection(), $tableNamePrefix); + $dimensionSpacePointsRepository = new DimensionSpacePointsRepository($this->dbalClient->getConnection(), $tableNames); $nodeFactory = new NodeFactory( $projectionFactoryDependencies->contentRepositoryId, @@ -56,7 +50,7 @@ public function build( $nodeFactory, $projectionFactoryDependencies->contentRepositoryId, $projectionFactoryDependencies->nodeTypeManager, - $tableNamePrefix + $tableNames ); return new ContentGraphProjection( @@ -64,9 +58,9 @@ public function build( $this->dbalClient, new ProjectionContentGraph( $this->dbalClient, - $tableNamePrefix + $tableNames->tableNamePrefix ), - $tableNamePrefix, + $tableNames, $dimensionSpacePointsRepository, new ContentGraphFinder($contentGraphFactory) ) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index c73be9afd58..eae660dc35a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -17,12 +17,9 @@ class DoctrineDbalContentGraphSchemaBuilder { private const DEFAULT_TEXT_COLLATION = 'utf8mb4_unicode_520_ci'; - private readonly ContentGraphTableNames $contentGraphTableNames; - public function __construct( - string $tableNamePrefix + private readonly ContentGraphTableNames $contentGraphTableNames ) { - $this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); } public function buildSchema(AbstractSchemaManager $schemaManager): Schema diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory.php index 2808fdf6301..a77588faffc 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory.php @@ -25,9 +25,9 @@ public function build( return new ProjectionIntegrityViolationDetectionRunner( new ProjectionIntegrityViolationDetector( $this->dbalClient, - DoctrineDbalContentGraphProjectionFactory::graphProjectionTableNamePrefix( + ContentGraphTableNames::create( $serviceFactoryDependencies->contentRepositoryId - ) + )->tableNamePrefix ) ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php index 8bc02f1e7d3..ae3c6632247 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php @@ -15,6 +15,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection; use Doctrine\DBAL\Connection; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTags; @@ -48,7 +49,7 @@ public function addToDatabase(Connection $databaseConnection, string $tableNameP $databaseConnection->transactional(function ($databaseConnection) use ( $tableNamePrefix ) { - $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, $tableNamePrefix); + $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, ContentGraphTableNames::withPrefix($tableNamePrefix)); $dimensionSpacePoints->insertDimensionSpacePoint($this->dimensionSpacePoint); $databaseConnection->insert($tableNamePrefix . '_hierarchyrelation', [ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php index f9bfb9f6d7d..d3c5f909491 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php @@ -16,6 +16,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\Types; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; use Neos\ContentRepository\Core\NodeType\NodeTypeName; @@ -148,7 +149,7 @@ public static function createNewInDatabase( $classification, $timestamps ) { - $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, $tableNamePrefix); + $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, ContentGraphTableNames::withPrefix($tableNamePrefix)); $dimensionSpacePoints->insertDimensionSpacePointByHashAndCoordinates($originDimensionSpacePointHash, $originDimensionSpacePoint); $databaseConnection->insert($tableNamePrefix . '_node', [ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 086f997aaaf..fb81c17eb03 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -19,6 +19,7 @@ use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; @@ -77,11 +78,11 @@ public function __construct( private readonly NodeFactory $nodeFactory, private readonly ContentRepositoryId $contentRepositoryId, private readonly NodeTypeManager $nodeTypeManager, - private readonly string $tableNamePrefix, + private readonly ContentGraphTableNames $tableNames, public readonly WorkspaceName $workspaceName, public readonly ContentStreamId $contentStreamId ) { - $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $this->tableNamePrefix); + $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $this->tableNames); } public function getSubgraph( @@ -100,7 +101,7 @@ public function getSubgraph( $this->client, $this->nodeFactory, $this->nodeTypeManager, - $this->tableNamePrefix + $this->tableNames ) ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index 262d117d7c7..ae3823d73b2 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -18,6 +18,7 @@ use Doctrine\DBAL\Exception as DbalException; use Doctrine\DBAL\ForwardCompatibility\Result; use Doctrine\DBAL\Query\QueryBuilder; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; @@ -99,9 +100,9 @@ public function __construct( private readonly DbalClientInterface $client, private readonly NodeFactory $nodeFactory, private readonly NodeTypeManager $nodeTypeManager, - string $tableNamePrefix + ContentGraphTableNames $tableNames ) { - $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $tableNamePrefix); + $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $tableNames); } public function getIdentity(): ContentSubgraphIdentity diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php index 374d750c3ad..5cedb5a21f3 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php @@ -31,13 +31,10 @@ final class DimensionSpacePointsRepository */ private array $dimensionspacePointsRuntimeCache = []; - private readonly ContentGraphTableNames $contentGraphTableNames; - public function __construct( private readonly Connection $databaseConnection, - string $tableNamePrefix + private readonly ContentGraphTableNames $contentGraphTableNames ) { - $this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); } public function insertDimensionSpacePoint(AbstractDimensionSpacePoint $dimensionSpacePoint): void diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index 42225dc29cc..d4cedfd70ae 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -33,13 +33,10 @@ */ final readonly class NodeQueryBuilder { - public ContentGraphTableNames $contentGraphTableNames; - public function __construct( private Connection $connection, - string $tableNamePrefix + public ContentGraphTableNames $contentGraphTableNames ) { - $this->contentGraphTableNames = ContentGraphTableNames::withPrefix($tableNamePrefix); } public function buildBasicNodeAggregateQuery(): QueryBuilder diff --git a/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php b/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php index 12a2152bbb6..2b1ce7fd096 100644 --- a/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php +++ b/Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php @@ -18,7 +18,6 @@ use Behat\Gherkin\Node\TableNode; use League\Flysystem\Filesystem; use League\Flysystem\InMemory\InMemoryFilesystemAdapter; -use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; @@ -206,11 +205,4 @@ public function iExpectAMigrationErrorWithTheMessage(PyStringNode $expectedMessa * @return T */ abstract private function getObject(string $className): object; - - protected function getTableNamePrefix(): string - { - return DoctrineDbalContentGraphProjectionFactory::graphProjectionTableNamePrefix( - $this->currentContentRepository->id - ); - } } From b74e010777a9914c27ad48bf159d6c5ddd1e8515 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 09:53:13 +0200 Subject: [PATCH 141/214] TASK: Refactor `HierarchyRelation` active record to use `ContentGraphTableNames` --- .../DoctrineDbalContentGraphProjection.php | 6 ++--- .../Domain/Projection/Feature/NodeMove.php | 6 ++--- .../Domain/Projection/Feature/NodeRemoval.php | 4 +--- .../Projection/Feature/NodeVariation.php | 16 ++++++------- .../Domain/Projection/HierarchyRelation.php | 24 +++++++++---------- 5 files changed, 25 insertions(+), 31 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 7a17423957d..7b4809019f3 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -464,7 +464,7 @@ private function connectHierarchy( $inheritedSubtreeTags, ); - $hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->tableNamePrefix); + $hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->contentGraphTableNames); } } @@ -551,7 +551,7 @@ private function getRelationPositionAfterRecalculation( $position = $offset; $offset += self::RELATION_DEFAULT_OFFSET; } - $relation->assignNewPosition($offset, $this->getDatabaseConnection(), $this->tableNamePrefix); + $relation->assignNewPosition($offset, $this->getDatabaseConnection(), $this->contentGraphTableNames); } return $position; @@ -766,7 +766,7 @@ protected function copyHierarchyRelationToDimensionSpacePoint( ), $inheritedSubtreeTags, ); - $copy->addToDatabase($this->getDatabaseConnection(), $this->tableNamePrefix); + $copy->addToDatabase($this->getDatabaseConnection(), $this->contentGraphTableNames); return $copy; } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php index fdf61f7eceb..4ca51a774fb 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php @@ -27,8 +27,6 @@ trait NodeMove { abstract protected function getProjectionContentGraph(): ProjectionContentGraph; - abstract protected function getTableNamePrefix(): string; - /** * @param NodeAggregateWasMoved $event * @throws \Throwable @@ -121,7 +119,7 @@ private function moveNodeBeforeSucceedingSibling( $ingoingHierarchyRelation->assignNewPosition( $newPosition, $this->getDatabaseConnection(), - $this->getTableNamePrefix() + $this->contentGraphTableNames ); } @@ -190,7 +188,7 @@ private function moveNodeBeneathParent( $newParent->relationAnchorPoint, $newPosition, $this->getDatabaseConnection(), - $this->getTableNamePrefix() + $this->contentGraphTableNames ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php index 86fbb00b696..6e038196fe3 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php @@ -20,8 +20,6 @@ trait NodeRemoval { abstract protected function getProjectionContentGraph(): ProjectionContentGraph; - abstract protected function getTableNamePrefix(): string; - protected LoggerInterface $systemLogger; /** @@ -51,7 +49,7 @@ private function whenNodeAggregateWasRemoved(NodeAggregateWasRemoved $event): vo protected function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes( HierarchyRelation $ingoingRelation ): void { - $ingoingRelation->removeFromDatabase($this->getDatabaseConnection(), $this->tableNamePrefix); + $ingoingRelation->removeFromDatabase($this->getDatabaseConnection(), $this->contentGraphTableNames); foreach ( $this->getProjectionContentGraph()->findOutgoingHierarchyRelationsForNode( diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php index 27b2322512e..5f6c9d74410 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php @@ -31,8 +31,6 @@ trait NodeVariation { abstract protected function getProjectionContentGraph(): ProjectionContentGraph; - abstract protected function getTableNamePrefix(): string; - /** * @param NodeSpecializationVariantWasCreated $event * @throws \Exception @@ -68,7 +66,7 @@ private function whenNodeSpecializationVariantWasCreated(NodeSpecializationVaria $hierarchyRelation->assignNewChildNode( $specializedNode->relationAnchorPoint, $this->getDatabaseConnection(), - $this->tableNamePrefix + $this->contentGraphTableNames ); unset($uncoveredDimensionSpacePoints[$hierarchyRelation->dimensionSpacePointHash]); } @@ -120,7 +118,7 @@ private function whenNodeSpecializationVariantWasCreated(NodeSpecializationVaria ), NodeTags::create(SubtreeTags::createEmpty(), $parentSubtreeTags->all()), ); - $hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->getTableNamePrefix()); + $hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->contentGraphTableNames); } } @@ -135,7 +133,7 @@ private function whenNodeSpecializationVariantWasCreated(NodeSpecializationVaria $specializedNode->relationAnchorPoint, null, $this->getDatabaseConnection(), - $this->getTableNamePrefix() + $this->contentGraphTableNames ); } @@ -189,7 +187,7 @@ public function whenNodeGeneralizationVariantWasCreated(NodeGeneralizationVarian $existingIngoingHierarchyRelation->assignNewChildNode( $generalizedNode->relationAnchorPoint, $this->getDatabaseConnection(), - $this->tableNamePrefix + $this->contentGraphTableNames ); $unassignedIngoingDimensionSpacePoints = $unassignedIngoingDimensionSpacePoints->getDifference( new DimensionSpacePointSet([ @@ -209,7 +207,7 @@ public function whenNodeGeneralizationVariantWasCreated(NodeGeneralizationVarian $generalizedNode->relationAnchorPoint, null, $this->getDatabaseConnection(), - $this->getTableNamePrefix() + $this->contentGraphTableNames ); } @@ -301,7 +299,7 @@ public function whenNodePeerVariantWasCreated(NodePeerVariantWasCreated $event, $existingIngoingHierarchyRelation->assignNewChildNode( $peerNode->relationAnchorPoint, $this->getDatabaseConnection(), - $this->tableNamePrefix + $this->contentGraphTableNames ); $unassignedIngoingDimensionSpacePoints = $unassignedIngoingDimensionSpacePoints->getDifference( new DimensionSpacePointSet([ @@ -321,7 +319,7 @@ public function whenNodePeerVariantWasCreated(NodePeerVariantWasCreated $event, $peerNode->relationAnchorPoint, null, $this->getDatabaseConnection(), - $this->getTableNamePrefix() + $this->contentGraphTableNames ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php index ae3c6632247..fe621a3a4d5 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php @@ -44,15 +44,15 @@ public function __construct( /** * @param Connection $databaseConnection */ - public function addToDatabase(Connection $databaseConnection, string $tableNamePrefix): void + public function addToDatabase(Connection $databaseConnection, ContentGraphTableNames $tableNames): void { $databaseConnection->transactional(function ($databaseConnection) use ( - $tableNamePrefix + $tableNames ) { - $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, ContentGraphTableNames::withPrefix($tableNamePrefix)); + $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, $tableNames); $dimensionSpacePoints->insertDimensionSpacePoint($this->dimensionSpacePoint); - $databaseConnection->insert($tableNamePrefix . '_hierarchyrelation', [ + $databaseConnection->insert($tableNames->hierachyRelation(), [ 'parentnodeanchor' => $this->parentNodeAnchor->value, 'childnodeanchor' => $this->childNodeAnchor->value, 'name' => $this->name?->value, @@ -67,9 +67,9 @@ public function addToDatabase(Connection $databaseConnection, string $tableNameP /** * @param Connection $databaseConnection */ - public function removeFromDatabase(Connection $databaseConnection, string $tableNamePrefix): void + public function removeFromDatabase(Connection $databaseConnection, ContentGraphTableNames $tableNames): void { - $databaseConnection->delete($tableNamePrefix . '_hierarchyrelation', $this->getDatabaseId()); + $databaseConnection->delete($tableNames->hierachyRelation(), $this->getDatabaseId()); } /** @@ -79,10 +79,10 @@ public function removeFromDatabase(Connection $databaseConnection, string $table public function assignNewChildNode( NodeRelationAnchorPoint $childAnchorPoint, Connection $databaseConnection, - string $tableNamePrefix + ContentGraphTableNames $tableNames ): void { $databaseConnection->update( - $tableNamePrefix . '_hierarchyrelation', + $tableNames->hierachyRelation(), [ 'childnodeanchor' => $childAnchorPoint->value ], @@ -97,7 +97,7 @@ public function assignNewParentNode( NodeRelationAnchorPoint $parentAnchorPoint, ?int $position, Connection $databaseConnection, - string $tableNamePrefix + ContentGraphTableNames $tableNames ): void { $data = [ 'parentnodeanchor' => $parentAnchorPoint->value @@ -106,16 +106,16 @@ public function assignNewParentNode( $data['position'] = $position; } $databaseConnection->update( - $tableNamePrefix . '_hierarchyrelation', + $tableNames->hierachyRelation(), $data, $this->getDatabaseId() ); } - public function assignNewPosition(int $position, Connection $databaseConnection, string $tableNamePrefix): void + public function assignNewPosition(int $position, Connection $databaseConnection, ContentGraphTableNames $tableNames): void { $databaseConnection->update( - $tableNamePrefix . '_hierarchyrelation', + $tableNames->hierachyRelation(), [ 'position' => $position ], From e130a86e1229f11274191ef1680740a1d9879671 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 09:56:30 +0200 Subject: [PATCH 142/214] TASK: Refactor `NodeRecord` active record to use `ContentGraphTableNames` --- .../src/ContentGraphTableNames.php | 11 ++--- .../DoctrineDbalContentGraphProjection.php | 12 +++--- .../src/Domain/Projection/NodeRecord.php | 43 ++++--------------- 3 files changed, 17 insertions(+), 49 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php index df37950d589..175eb07be52 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -10,8 +10,9 @@ */ final readonly class ContentGraphTableNames { - private function __construct(public string $tableNamePrefix) - { + private function __construct( + public string $tableNamePrefix + ) { } public static function create(ContentRepositoryId $contentRepositoryId): self @@ -19,12 +20,6 @@ public static function create(ContentRepositoryId $contentRepositoryId): self return new self(sprintf('cr_%s_p_graph', $contentRepositoryId->value)); } - /** @deprecated */ - public static function withPrefix(string $tableNamePrefix): self - { - return new self($tableNamePrefix); - } - public function node(): string { return $this->tableNamePrefix . '_node'; diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 7b4809019f3..7b0ec9a17a6 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -236,7 +236,7 @@ private function whenRootNodeAggregateWithNodeWasCreated(RootNodeAggregateWithNo $originDimensionSpacePoint = OriginDimensionSpacePoint::createWithoutDimensions(); $node = NodeRecord::createNewInDatabase( $this->getDatabaseConnection(), - $this->tableNamePrefix, + $this->contentGraphTableNames, $event->nodeAggregateId, $originDimensionSpacePoint->coordinates, $originDimensionSpacePoint->hash, @@ -372,7 +372,7 @@ private function createNodeWithHierarchy( ): void { $node = NodeRecord::createNewInDatabase( $this->getDatabaseConnection(), - $this->tableNamePrefix, + $this->contentGraphTableNames, $nodeAggregateId, $originDimensionSpacePoint->jsonSerialize(), $originDimensionSpacePoint->hash, @@ -781,7 +781,7 @@ protected function copyNodeToDimensionSpacePoint( ): NodeRecord { return NodeRecord::createNewInDatabase( $this->getDatabaseConnection(), - $this->tableNamePrefix, + $this->contentGraphTableNames, $sourceNode->nodeAggregateId, $originDimensionSpacePoint->coordinates, $originDimensionSpacePoint->hash, @@ -833,9 +833,9 @@ private function updateNodeRecordWithCopyOnWrite( // 1) fetch node, adjust properties, assign new Relation Anchor Point /** @var NodeRecord $originalNode The anchor point appears in a content stream, so there must be a node */ $originalNode = $this->projectionContentGraph->getNodeByAnchorPoint($anchorPoint); - $copiedNode = NodeRecord::createCopyFromNodeRecord($this->getDatabaseConnection(), $this->tableNamePrefix, $originalNode); + $copiedNode = NodeRecord::createCopyFromNodeRecord($this->getDatabaseConnection(), $this->contentGraphTableNames, $originalNode); $result = $operations($copiedNode); - $copiedNode->updateToDatabase($this->getDatabaseConnection(), $this->tableNamePrefix); + $copiedNode->updateToDatabase($this->getDatabaseConnection(), $this->contentGraphTableNames); // 2) reconnect all edges belonging to this content stream to the new "copied node". // IMPORTANT: We need to reconnect BOTH the incoming and outgoing edges. @@ -874,7 +874,7 @@ private function updateNodeRecordWithCopyOnWrite( } $result = $operations($node); - $node->updateToDatabase($this->getDatabaseConnection(), $this->tableNamePrefix); + $node->updateToDatabase($this->getDatabaseConnection(), $this->contentGraphTableNames); } return $result; } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php index d3c5f909491..6ce216ba365 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php @@ -48,13 +48,12 @@ public function __construct( } /** - * @param Connection $databaseConnection * @throws \Doctrine\DBAL\DBALException */ - public function updateToDatabase(Connection $databaseConnection, string $tableNamePrefix): void + public function updateToDatabase(Connection $databaseConnection, ContentGraphTableNames $tableNames): void { $databaseConnection->update( - $tableNamePrefix . '_node', + $tableNames->node(), [ 'nodeaggregateid' => $this->nodeAggregateId->value, 'origindimensionspacepointhash' => $this->originDimensionSpacePointHash, @@ -74,18 +73,6 @@ public function updateToDatabase(Connection $databaseConnection, string $tableNa ); } - /** - * @param Connection $databaseConnection - * @throws \Doctrine\DBAL\DBALException - * @throws \Doctrine\DBAL\Exception\InvalidArgumentException - */ - public function removeFromDatabase(Connection $databaseConnection, string $tableNamePrefix): void - { - $databaseConnection->delete($tableNamePrefix . '_node', [ - 'relationanchorpoint' => $this->relationAnchorPoint->value - ]); - } - /** * @param array $databaseRow * @throws \Exception @@ -113,22 +100,12 @@ public static function fromDatabaseRow(array $databaseRow): self /** * Insert a node record with the given data and return it. * - * @param Connection $databaseConnection - * @param string $tableNamePrefix - * @param NodeAggregateId $nodeAggregateId * @param array $originDimensionSpacePoint - * @param string $originDimensionSpacePointHash - * @param SerializedPropertyValues $properties - * @param NodeTypeName $nodeTypeName - * @param NodeAggregateClassification $classification - * @param NodeName|null $nodeName - * @param Timestamps $timestamps - * @return self * @throws \Doctrine\DBAL\Exception */ public static function createNewInDatabase( Connection $databaseConnection, - string $tableNamePrefix, + ContentGraphTableNames $tableNames, NodeAggregateId $nodeAggregateId, array $originDimensionSpacePoint, string $originDimensionSpacePointHash, @@ -140,7 +117,7 @@ public static function createNewInDatabase( Timestamps $timestamps, ): self { $relationAnchorPoint = $databaseConnection->transactional(function ($databaseConnection) use ( - $tableNamePrefix, + $tableNames, $nodeAggregateId, $originDimensionSpacePoint, $originDimensionSpacePointHash, @@ -149,10 +126,10 @@ public static function createNewInDatabase( $classification, $timestamps ) { - $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, ContentGraphTableNames::withPrefix($tableNamePrefix)); + $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, $tableNames); $dimensionSpacePoints->insertDimensionSpacePointByHashAndCoordinates($originDimensionSpacePointHash, $originDimensionSpacePoint); - $databaseConnection->insert($tableNamePrefix . '_node', [ + $databaseConnection->insert($tableNames->node(), [ 'nodeaggregateid' => $nodeAggregateId->value, 'origindimensionspacepointhash' => $originDimensionSpacePointHash, 'properties' => json_encode($properties), @@ -188,20 +165,16 @@ public static function createNewInDatabase( /** * Creates a copy of this NodeRecord with a new anchor point. * - * @param Connection $databaseConnection - * @param string $tableNamePrefix - * @param NodeRecord $copyFrom - * @return self * @throws \Doctrine\DBAL\Exception */ public static function createCopyFromNodeRecord( Connection $databaseConnection, - string $tableNamePrefix, + ContentGraphTableNames $tableNames, NodeRecord $copyFrom ): self { return self::createNewInDatabase( $databaseConnection, - $tableNamePrefix, + $tableNames, $copyFrom->nodeAggregateId, $copyFrom->originDimensionSpacePoint, $copyFrom->originDimensionSpacePointHash, From 723bea35daed476c63a3fad3ddea4a2bfdc01160 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 10:02:25 +0200 Subject: [PATCH 143/214] TASK: Remove string based `tableNamePrefix` from `DoctrineDbalContentGraphProjection` --- .../DoctrineDbalContentGraphProjection.php | 9 ----- .../Domain/Projection/Feature/NodeRemoval.php | 6 +-- .../Projection/Feature/SubtreeTagging.php | 40 +++++++++---------- 3 files changed, 22 insertions(+), 33 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 7b0ec9a17a6..9b6414e2d0a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -75,9 +75,6 @@ final class DoctrineDbalContentGraphProjection implements ProjectionInterface private DbalCheckpointStorage $checkpointStorage; - /** @deprecated */ - private readonly string $tableNamePrefix; - public function __construct( private readonly DbalClientInterface $dbalClient, private readonly ProjectionContentGraph $projectionContentGraph, @@ -85,7 +82,6 @@ public function __construct( private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository, private readonly ContentGraphFinder $contentGraphFinder ) { - $this->tableNamePrefix = $this->contentGraphTableNames->tableNamePrefix; $this->checkpointStorage = new DbalCheckpointStorage( $this->dbalClient->getConnection(), $this->contentGraphTableNames->checkpoint(), @@ -98,11 +94,6 @@ protected function getProjectionContentGraph(): ProjectionContentGraph return $this->projectionContentGraph; } - protected function getTableNamePrefix(): string - { - return $this->tableNamePrefix; - } - public function setUp(): void { foreach ($this->determineRequiredSqlStatements() as $statement) { diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php index 6e038196fe3..6486e3ac490 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php @@ -65,11 +65,11 @@ protected function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNo // also remove outbound reference relations $this->getDatabaseConnection()->executeStatement( ' - DELETE n, r FROM ' . $this->getTableNamePrefix() . '_node n - LEFT JOIN ' . $this->getTableNamePrefix() . '_referencerelation r + DELETE n, r FROM ' . $this->contentGraphTableNames->node() . ' n + LEFT JOIN ' . $this->contentGraphTableNames->referenceRelation() . ' r ON r.nodeanchorpoint = n.relationanchorpoint LEFT JOIN - ' . $this->getTableNamePrefix() . '_hierarchyrelation h + ' . $this->contentGraphTableNames->hierachyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE n.relationanchorpoint = :anchorPointForNode diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 25887ab2575..bb21c94cc79 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -21,21 +21,19 @@ */ trait SubtreeTagging { - abstract protected function getTableNamePrefix(): string; - /** * @throws \Throwable */ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h + UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h SET h.subtreetags = JSON_INSERT(h.subtreetags, :tagPath, null) WHERE h.childnodeanchor IN ( WITH RECURSIVE cte (id) AS ( SELECT ch.childnodeanchor - FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation ch - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = ch.parentnodeanchor + FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' ch + INNER JOIN ' . $this->contentGraphTableNames->node() . ' n ON n.relationanchorpoint = ch.parentnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId AND ch.contentstreamid = :contentStreamId @@ -46,7 +44,7 @@ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void dh.childnodeanchor FROM cte - JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.id + JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' dh ON dh.parentnodeanchor = cte.id WHERE NOT JSON_CONTAINS_PATH(dh.subtreetags, \'one\', :tagPath) ) @@ -64,8 +62,8 @@ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void ]); $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = h.childnodeanchor + UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h + INNER JOIN ' . $this->contentGraphTableNames->node() . ' n ON n.relationanchorpoint = h.childnodeanchor SET h.subtreetags = JSON_SET(h.subtreetags, :tagPath, true) WHERE n.nodeaggregateid = :nodeAggregateId @@ -87,15 +85,15 @@ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h - INNER JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation ph ON ph.childnodeanchor = h.parentnodeanchor + UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h + INNER JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' ph ON ph.childnodeanchor = h.parentnodeanchor SET h.subtreetags = IF(( SELECT JSON_CONTAINS_PATH(ph.subtreetags, \'one\', :tagPath) FROM - ' . $this->getTableNamePrefix() . '_hierarchyrelation ph - INNER JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation ch ON ch.parentnodeanchor = ph.childnodeanchor - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = ch.childnodeanchor + ' . $this->contentGraphTableNames->hierachyRelation() . ' ph + INNER JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' ch ON ch.parentnodeanchor = ph.childnodeanchor + INNER JOIN ' . $this->contentGraphTableNames->node() . ' n ON n.relationanchorpoint = ch.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId AND ph.contentstreamid = :contentStreamId @@ -105,8 +103,8 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void WHERE h.childnodeanchor IN ( WITH RECURSIVE cte (id) AS ( SELECT ch.childnodeanchor - FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation ch - INNER JOIN ' . $this->getTableNamePrefix() . '_node n ON n.relationanchorpoint = ch.childnodeanchor + FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' ch + INNER JOIN ' . $this->contentGraphTableNames->node() . ' n ON n.relationanchorpoint = ch.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId AND ch.contentstreamid = :contentStreamId @@ -116,7 +114,7 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void dh.childnodeanchor FROM cte - JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh ON dh.parentnodeanchor = cte.id + JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' dh ON dh.parentnodeanchor = cte.id WHERE JSON_EXTRACT(dh.subtreetags, :tagPath) != TRUE ) @@ -140,14 +138,14 @@ private function moveSubtreeTags( DimensionSpacePoint $coveredDimensionSpacePoint ): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->getTableNamePrefix() . '_hierarchyrelation h, + UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h, ( WITH RECURSIVE cte AS ( SELECT JSON_KEYS(th.subtreetags) subtreeTagsToInherit, th.childnodeanchor FROM - ' . $this->getTableNamePrefix() . '_hierarchyrelation th - INNER JOIN ' . $this->getTableNamePrefix() . '_node tn ON tn.relationanchorpoint = th.childnodeanchor + ' . $this->contentGraphTableNames->hierachyRelation() . ' th + INNER JOIN ' . $this->contentGraphTableNames->node() . ' tn ON tn.relationanchorpoint = th.childnodeanchor WHERE tn.nodeaggregateid = :newParentNodeAggregateId AND th.contentstreamid = :contentStreamId @@ -164,7 +162,7 @@ private function moveSubtreeTags( dh.childnodeanchor FROM cte - JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation dh + JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' dh ON dh.parentnodeanchor = cte.childnodeanchor AND dh.contentstreamid = :contentStreamId @@ -198,7 +196,7 @@ private function subtreeTagsForHierarchyRelation(ContentStreamId $contentStreamI return NodeTags::createEmpty(); } $subtreeTagsJson = $this->getDatabaseConnection()->fetchOne(' - SELECT h.subtreetags FROM ' . $this->getTableNamePrefix() . '_hierarchyrelation h + SELECT h.subtreetags FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' h WHERE h.childnodeanchor = :parentNodeAnchorPoint AND h.contentstreamid = :contentStreamId From 14331e686fa4fffe05fa4ec67fc3bdaabdad5524 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 10:05:35 +0200 Subject: [PATCH 144/214] TASK: Rename `$contentGraphTableNames` to `$tableNames` --- .../DoctrineDbalContentGraphProjection.php | 80 +++++++++---------- .../Domain/Projection/Feature/NodeMove.php | 4 +- .../Domain/Projection/Feature/NodeRemoval.php | 8 +- .../Projection/Feature/NodeVariation.php | 14 ++-- .../Projection/Feature/SubtreeTagging.php | 38 ++++----- .../src/Domain/Repository/ContentGraph.php | 26 +++--- .../src/Domain/Repository/ContentSubgraph.php | 50 ++++++------ .../DimensionSpacePointsRepository.php | 6 +- .../src/NodeQueryBuilder.php | 42 +++++----- 9 files changed, 134 insertions(+), 134 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 9b6414e2d0a..0d67b426ef1 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -78,13 +78,13 @@ final class DoctrineDbalContentGraphProjection implements ProjectionInterface public function __construct( private readonly DbalClientInterface $dbalClient, private readonly ProjectionContentGraph $projectionContentGraph, - private readonly ContentGraphTableNames $contentGraphTableNames, + private readonly ContentGraphTableNames $tableNames, private readonly DimensionSpacePointsRepository $dimensionSpacePointsRepository, private readonly ContentGraphFinder $contentGraphFinder ) { $this->checkpointStorage = new DbalCheckpointStorage( $this->dbalClient->getConnection(), - $this->contentGraphTableNames->checkpoint(), + $this->tableNames->checkpoint(), self::class ); } @@ -112,7 +112,7 @@ private function determineRequiredSqlStatements(): array if (!$schemaManager instanceof AbstractSchemaManager) { throw new \RuntimeException('Failed to retrieve Schema Manager', 1625653914); } - $schema = (new DoctrineDbalContentGraphSchemaBuilder($this->contentGraphTableNames))->buildSchema($schemaManager); + $schema = (new DoctrineDbalContentGraphSchemaBuilder($this->tableNames))->buildSchema($schemaManager); return DbalSchemaDiff::determineRequiredSqlStatements($connection, $schema); } @@ -153,10 +153,10 @@ public function reset(): void private function truncateDatabaseTables(): void { $connection = $this->dbalClient->getConnection(); - $connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->node()); - $connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->hierachyRelation()); - $connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->referenceRelation()); - $connection->executeQuery('TRUNCATE table ' . $this->contentGraphTableNames->dimensionSpacePoints()); + $connection->executeQuery('TRUNCATE table ' . $this->tableNames->node()); + $connection->executeQuery('TRUNCATE table ' . $this->tableNames->hierachyRelation()); + $connection->executeQuery('TRUNCATE table ' . $this->tableNames->referenceRelation()); + $connection->executeQuery('TRUNCATE table ' . $this->tableNames->dimensionSpacePoints()); } public function canHandle(EventInterface $event): bool @@ -227,7 +227,7 @@ private function whenRootNodeAggregateWithNodeWasCreated(RootNodeAggregateWithNo $originDimensionSpacePoint = OriginDimensionSpacePoint::createWithoutDimensions(); $node = NodeRecord::createNewInDatabase( $this->getDatabaseConnection(), - $this->contentGraphTableNames, + $this->tableNames, $event->nodeAggregateId, $originDimensionSpacePoint->coordinates, $originDimensionSpacePoint->hash, @@ -273,7 +273,7 @@ private function whenRootNodeAggregateDimensionsWereUpdated(RootNodeAggregateDim $this->transactional(function () use ($rootNodeAnchorPoint, $event) { // delete all hierarchy edges of the root node $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' + DELETE FROM ' . $this->tableNames->hierachyRelation() . ' WHERE parentnodeanchor = :parentNodeAnchor AND childnodeanchor = :childNodeAnchor @@ -322,8 +322,8 @@ private function whenNodeAggregateNameWasChanged(NodeAggregateNameWasChanged $ev { $this->transactional(function () use ($event, $eventEnvelope) { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h - INNER JOIN ' . $this->contentGraphTableNames->node() . ' n on + UPDATE ' . $this->tableNames->hierachyRelation() . ' h + INNER JOIN ' . $this->tableNames->node() . ' n on h.childnodeanchor = n.relationanchorpoint SET h.name = :newName, @@ -363,7 +363,7 @@ private function createNodeWithHierarchy( ): void { $node = NodeRecord::createNewInDatabase( $this->getDatabaseConnection(), - $this->contentGraphTableNames, + $this->tableNames, $nodeAggregateId, $originDimensionSpacePoint->jsonSerialize(), $originDimensionSpacePoint->hash, @@ -455,7 +455,7 @@ private function connectHierarchy( $inheritedSubtreeTags, ); - $hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->contentGraphTableNames); + $hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->tableNames); } } @@ -542,7 +542,7 @@ private function getRelationPositionAfterRecalculation( $position = $offset; $offset += self::RELATION_DEFAULT_OFFSET; } - $relation->assignNewPosition($offset, $this->getDatabaseConnection(), $this->contentGraphTableNames); + $relation->assignNewPosition($offset, $this->getDatabaseConnection(), $this->tableNames); } return $position; @@ -559,7 +559,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void // 1) Copy HIERARCHY RELATIONS (this is the MAIN OPERATION here) // $this->getDatabaseConnection()->executeUpdate(' - INSERT INTO ' . $this->contentGraphTableNames->hierachyRelation() . ' ( + INSERT INTO ' . $this->tableNames->hierachyRelation() . ' ( parentnodeanchor, childnodeanchor, `name`, @@ -577,7 +577,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void h.subtreetags, "' . $event->newContentStreamId->value . '" AS contentstreamid FROM - ' . $this->contentGraphTableNames->hierachyRelation() . ' h + ' . $this->tableNames->hierachyRelation() . ' h WHERE h.contentstreamid = :sourceContentStreamId ', [ 'sourceContentStreamId' => $event->sourceContentStreamId->value @@ -594,7 +594,7 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo // Drop hierarchy relations $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' + DELETE FROM ' . $this->tableNames->hierachyRelation() . ' WHERE contentstreamid = :contentStreamId ', [ @@ -603,23 +603,23 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo // Drop non-referenced nodes (which do not have a hierarchy relation anymore) $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->contentGraphTableNames->node() . ' + DELETE FROM ' . $this->tableNames->node() . ' WHERE NOT EXISTS ( - SELECT 1 FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' - WHERE ' . $this->contentGraphTableNames->hierachyRelation() . '.childnodeanchor - = ' . $this->contentGraphTableNames->node() . '.relationanchorpoint + SELECT 1 FROM ' . $this->tableNames->hierachyRelation() . ' + WHERE ' . $this->tableNames->hierachyRelation() . '.childnodeanchor + = ' . $this->tableNames->node() . '.relationanchorpoint ) '); // Drop non-referenced reference relations (i.e. because the referenced nodes are gone by now) $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->contentGraphTableNames->referenceRelation() . ' + DELETE FROM ' . $this->tableNames->referenceRelation() . ' WHERE NOT EXISTS ( - SELECT 1 FROM ' . $this->contentGraphTableNames->node() . ' - WHERE ' . $this->contentGraphTableNames->node() . '.relationanchorpoint - = ' . $this->contentGraphTableNames->referenceRelation() . '.nodeanchorpoint + SELECT 1 FROM ' . $this->tableNames->node() . ' + WHERE ' . $this->tableNames->node() . '.relationanchorpoint + = ' . $this->tableNames->referenceRelation() . '.nodeanchorpoint ) '); }); @@ -704,7 +704,7 @@ function (NodeRecord $node) use ($eventEnvelope) { ); // remove old - $this->getDatabaseConnection()->delete($this->contentGraphTableNames->referenceRelation(), [ + $this->getDatabaseConnection()->delete($this->tableNames->referenceRelation(), [ 'nodeanchorpoint' => $nodeAnchorPoint?->value, 'name' => $event->referenceName->value ]); @@ -713,7 +713,7 @@ function (NodeRecord $node) use ($eventEnvelope) { $position = 0; /** @var SerializedNodeReference $reference */ foreach ($event->references as $reference) { - $this->getDatabaseConnection()->insert($this->contentGraphTableNames->referenceRelation(), [ + $this->getDatabaseConnection()->insert($this->tableNames->referenceRelation(), [ 'name' => $event->referenceName->value, 'position' => $position, 'nodeanchorpoint' => $nodeAnchorPoint?->value, @@ -757,7 +757,7 @@ protected function copyHierarchyRelationToDimensionSpacePoint( ), $inheritedSubtreeTags, ); - $copy->addToDatabase($this->getDatabaseConnection(), $this->contentGraphTableNames); + $copy->addToDatabase($this->getDatabaseConnection(), $this->tableNames); return $copy; } @@ -772,7 +772,7 @@ protected function copyNodeToDimensionSpacePoint( ): NodeRecord { return NodeRecord::createNewInDatabase( $this->getDatabaseConnection(), - $this->contentGraphTableNames, + $this->tableNames, $sourceNode->nodeAggregateId, $originDimensionSpacePoint->coordinates, $originDimensionSpacePoint->hash, @@ -824,15 +824,15 @@ private function updateNodeRecordWithCopyOnWrite( // 1) fetch node, adjust properties, assign new Relation Anchor Point /** @var NodeRecord $originalNode The anchor point appears in a content stream, so there must be a node */ $originalNode = $this->projectionContentGraph->getNodeByAnchorPoint($anchorPoint); - $copiedNode = NodeRecord::createCopyFromNodeRecord($this->getDatabaseConnection(), $this->contentGraphTableNames, $originalNode); + $copiedNode = NodeRecord::createCopyFromNodeRecord($this->getDatabaseConnection(), $this->tableNames, $originalNode); $result = $operations($copiedNode); - $copiedNode->updateToDatabase($this->getDatabaseConnection(), $this->contentGraphTableNames); + $copiedNode->updateToDatabase($this->getDatabaseConnection(), $this->tableNames); // 2) reconnect all edges belonging to this content stream to the new "copied node". // IMPORTANT: We need to reconnect BOTH the incoming and outgoing edges. $this->getDatabaseConnection()->executeStatement( ' - UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h + UPDATE ' . $this->tableNames->hierachyRelation() . ' h SET -- if our (copied) node is the child, we update h.childNodeAnchor h.childnodeanchor @@ -865,7 +865,7 @@ private function updateNodeRecordWithCopyOnWrite( } $result = $operations($node); - $node->updateToDatabase($this->getDatabaseConnection(), $this->contentGraphTableNames); + $node->updateToDatabase($this->getDatabaseConnection(), $this->tableNames); } return $result; } @@ -876,7 +876,7 @@ private function copyReferenceRelations( NodeRelationAnchorPoint $destinationRelationAnchorPoint ): void { $this->getDatabaseConnection()->executeStatement(' - INSERT INTO ' . $this->contentGraphTableNames->referenceRelation() . ' ( + INSERT INTO ' . $this->tableNames->referenceRelation() . ' ( nodeanchorpoint, name, position, @@ -888,7 +888,7 @@ private function copyReferenceRelations( ref.position, ref.destinationnodeaggregateid FROM - ' . $this->contentGraphTableNames->referenceRelation() . ' ref + ' . $this->tableNames->referenceRelation() . ' ref WHERE ref.nodeanchorpoint = :sourceNodeAnchorPoint ', [ 'sourceNodeAnchorPoint' => $sourceRelationAnchorPoint->value, @@ -907,8 +907,8 @@ private function whenDimensionSpacePointWasMoved(DimensionSpacePointWasMoved $ev // 1) originDimensionSpacePoint on Node $rel = $this->getDatabaseConnection()->executeQuery( 'SELECT n.relationanchorpoint, n.origindimensionspacepointhash - FROM ' . $this->contentGraphTableNames->node() . ' n - INNER JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' h + FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierachyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint AND h.contentstreamid = :contentStreamId @@ -937,7 +937,7 @@ function (NodeRecord $nodeRecord) use ($event) { // 2) hierarchy relations $this->getDatabaseConnection()->executeStatement( ' - UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h + UPDATE ' . $this->tableNames->hierachyRelation() . ' h SET h.dimensionspacepointhash = :newDimensionSpacePointHash WHERE @@ -961,7 +961,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded // 1) hierarchy relations $this->getDatabaseConnection()->executeStatement( ' - INSERT INTO ' . $this->contentGraphTableNames->hierachyRelation() . ' ( + INSERT INTO ' . $this->tableNames->hierachyRelation() . ' ( parentnodeanchor, childnodeanchor, `name`, @@ -979,7 +979,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded :newDimensionSpacePointHash AS dimensionspacepointhash, h.contentstreamid FROM - ' . $this->contentGraphTableNames->hierachyRelation() . ' h + ' . $this->tableNames->hierachyRelation() . ' h WHERE h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :sourceDimensionSpacePointHash', [ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php index 4ca51a774fb..efa4f0badd9 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeMove.php @@ -119,7 +119,7 @@ private function moveNodeBeforeSucceedingSibling( $ingoingHierarchyRelation->assignNewPosition( $newPosition, $this->getDatabaseConnection(), - $this->contentGraphTableNames + $this->tableNames ); } @@ -188,7 +188,7 @@ private function moveNodeBeneathParent( $newParent->relationAnchorPoint, $newPosition, $this->getDatabaseConnection(), - $this->contentGraphTableNames + $this->tableNames ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php index 6486e3ac490..96012716631 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php @@ -49,7 +49,7 @@ private function whenNodeAggregateWasRemoved(NodeAggregateWasRemoved $event): vo protected function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNodes( HierarchyRelation $ingoingRelation ): void { - $ingoingRelation->removeFromDatabase($this->getDatabaseConnection(), $this->contentGraphTableNames); + $ingoingRelation->removeFromDatabase($this->getDatabaseConnection(), $this->tableNames); foreach ( $this->getProjectionContentGraph()->findOutgoingHierarchyRelationsForNode( @@ -65,11 +65,11 @@ protected function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNo // also remove outbound reference relations $this->getDatabaseConnection()->executeStatement( ' - DELETE n, r FROM ' . $this->contentGraphTableNames->node() . ' n - LEFT JOIN ' . $this->contentGraphTableNames->referenceRelation() . ' r + DELETE n, r FROM ' . $this->tableNames->node() . ' n + LEFT JOIN ' . $this->tableNames->referenceRelation() . ' r ON r.nodeanchorpoint = n.relationanchorpoint LEFT JOIN - ' . $this->contentGraphTableNames->hierachyRelation() . ' h + ' . $this->tableNames->hierachyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE n.relationanchorpoint = :anchorPointForNode diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php index 5f6c9d74410..15f11b3ff1a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeVariation.php @@ -66,7 +66,7 @@ private function whenNodeSpecializationVariantWasCreated(NodeSpecializationVaria $hierarchyRelation->assignNewChildNode( $specializedNode->relationAnchorPoint, $this->getDatabaseConnection(), - $this->contentGraphTableNames + $this->tableNames ); unset($uncoveredDimensionSpacePoints[$hierarchyRelation->dimensionSpacePointHash]); } @@ -118,7 +118,7 @@ private function whenNodeSpecializationVariantWasCreated(NodeSpecializationVaria ), NodeTags::create(SubtreeTags::createEmpty(), $parentSubtreeTags->all()), ); - $hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->contentGraphTableNames); + $hierarchyRelation->addToDatabase($this->getDatabaseConnection(), $this->tableNames); } } @@ -133,7 +133,7 @@ private function whenNodeSpecializationVariantWasCreated(NodeSpecializationVaria $specializedNode->relationAnchorPoint, null, $this->getDatabaseConnection(), - $this->contentGraphTableNames + $this->tableNames ); } @@ -187,7 +187,7 @@ public function whenNodeGeneralizationVariantWasCreated(NodeGeneralizationVarian $existingIngoingHierarchyRelation->assignNewChildNode( $generalizedNode->relationAnchorPoint, $this->getDatabaseConnection(), - $this->contentGraphTableNames + $this->tableNames ); $unassignedIngoingDimensionSpacePoints = $unassignedIngoingDimensionSpacePoints->getDifference( new DimensionSpacePointSet([ @@ -207,7 +207,7 @@ public function whenNodeGeneralizationVariantWasCreated(NodeGeneralizationVarian $generalizedNode->relationAnchorPoint, null, $this->getDatabaseConnection(), - $this->contentGraphTableNames + $this->tableNames ); } @@ -299,7 +299,7 @@ public function whenNodePeerVariantWasCreated(NodePeerVariantWasCreated $event, $existingIngoingHierarchyRelation->assignNewChildNode( $peerNode->relationAnchorPoint, $this->getDatabaseConnection(), - $this->contentGraphTableNames + $this->tableNames ); $unassignedIngoingDimensionSpacePoints = $unassignedIngoingDimensionSpacePoints->getDifference( new DimensionSpacePointSet([ @@ -319,7 +319,7 @@ public function whenNodePeerVariantWasCreated(NodePeerVariantWasCreated $event, $peerNode->relationAnchorPoint, null, $this->getDatabaseConnection(), - $this->contentGraphTableNames + $this->tableNames ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index bb21c94cc79..1373303a5b1 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -27,13 +27,13 @@ trait SubtreeTagging private function whenSubtreeWasTagged(SubtreeWasTagged $event): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h + UPDATE ' . $this->tableNames->hierachyRelation() . ' h SET h.subtreetags = JSON_INSERT(h.subtreetags, :tagPath, null) WHERE h.childnodeanchor IN ( WITH RECURSIVE cte (id) AS ( SELECT ch.childnodeanchor - FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' ch - INNER JOIN ' . $this->contentGraphTableNames->node() . ' n ON n.relationanchorpoint = ch.parentnodeanchor + FROM ' . $this->tableNames->hierachyRelation() . ' ch + INNER JOIN ' . $this->tableNames->node() . ' n ON n.relationanchorpoint = ch.parentnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId AND ch.contentstreamid = :contentStreamId @@ -44,7 +44,7 @@ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void dh.childnodeanchor FROM cte - JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' dh ON dh.parentnodeanchor = cte.id + JOIN ' . $this->tableNames->hierachyRelation() . ' dh ON dh.parentnodeanchor = cte.id WHERE NOT JSON_CONTAINS_PATH(dh.subtreetags, \'one\', :tagPath) ) @@ -62,8 +62,8 @@ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void ]); $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h - INNER JOIN ' . $this->contentGraphTableNames->node() . ' n ON n.relationanchorpoint = h.childnodeanchor + UPDATE ' . $this->tableNames->hierachyRelation() . ' h + INNER JOIN ' . $this->tableNames->node() . ' n ON n.relationanchorpoint = h.childnodeanchor SET h.subtreetags = JSON_SET(h.subtreetags, :tagPath, true) WHERE n.nodeaggregateid = :nodeAggregateId @@ -85,15 +85,15 @@ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h - INNER JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' ph ON ph.childnodeanchor = h.parentnodeanchor + UPDATE ' . $this->tableNames->hierachyRelation() . ' h + INNER JOIN ' . $this->tableNames->hierachyRelation() . ' ph ON ph.childnodeanchor = h.parentnodeanchor SET h.subtreetags = IF(( SELECT JSON_CONTAINS_PATH(ph.subtreetags, \'one\', :tagPath) FROM - ' . $this->contentGraphTableNames->hierachyRelation() . ' ph - INNER JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' ch ON ch.parentnodeanchor = ph.childnodeanchor - INNER JOIN ' . $this->contentGraphTableNames->node() . ' n ON n.relationanchorpoint = ch.childnodeanchor + ' . $this->tableNames->hierachyRelation() . ' ph + INNER JOIN ' . $this->tableNames->hierachyRelation() . ' ch ON ch.parentnodeanchor = ph.childnodeanchor + INNER JOIN ' . $this->tableNames->node() . ' n ON n.relationanchorpoint = ch.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId AND ph.contentstreamid = :contentStreamId @@ -103,8 +103,8 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void WHERE h.childnodeanchor IN ( WITH RECURSIVE cte (id) AS ( SELECT ch.childnodeanchor - FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' ch - INNER JOIN ' . $this->contentGraphTableNames->node() . ' n ON n.relationanchorpoint = ch.childnodeanchor + FROM ' . $this->tableNames->hierachyRelation() . ' ch + INNER JOIN ' . $this->tableNames->node() . ' n ON n.relationanchorpoint = ch.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId AND ch.contentstreamid = :contentStreamId @@ -114,7 +114,7 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void dh.childnodeanchor FROM cte - JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' dh ON dh.parentnodeanchor = cte.id + JOIN ' . $this->tableNames->hierachyRelation() . ' dh ON dh.parentnodeanchor = cte.id WHERE JSON_EXTRACT(dh.subtreetags, :tagPath) != TRUE ) @@ -138,14 +138,14 @@ private function moveSubtreeTags( DimensionSpacePoint $coveredDimensionSpacePoint ): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->contentGraphTableNames->hierachyRelation() . ' h, + UPDATE ' . $this->tableNames->hierachyRelation() . ' h, ( WITH RECURSIVE cte AS ( SELECT JSON_KEYS(th.subtreetags) subtreeTagsToInherit, th.childnodeanchor FROM - ' . $this->contentGraphTableNames->hierachyRelation() . ' th - INNER JOIN ' . $this->contentGraphTableNames->node() . ' tn ON tn.relationanchorpoint = th.childnodeanchor + ' . $this->tableNames->hierachyRelation() . ' th + INNER JOIN ' . $this->tableNames->node() . ' tn ON tn.relationanchorpoint = th.childnodeanchor WHERE tn.nodeaggregateid = :newParentNodeAggregateId AND th.contentstreamid = :contentStreamId @@ -162,7 +162,7 @@ private function moveSubtreeTags( dh.childnodeanchor FROM cte - JOIN ' . $this->contentGraphTableNames->hierachyRelation() . ' dh + JOIN ' . $this->tableNames->hierachyRelation() . ' dh ON dh.parentnodeanchor = cte.childnodeanchor AND dh.contentstreamid = :contentStreamId @@ -196,7 +196,7 @@ private function subtreeTagsForHierarchyRelation(ContentStreamId $contentStreamI return NodeTags::createEmpty(); } $subtreeTagsJson = $this->getDatabaseConnection()->fetchOne(' - SELECT h.subtreetags FROM ' . $this->contentGraphTableNames->hierachyRelation() . ' h + SELECT h.subtreetags FROM ' . $this->tableNames->hierachyRelation() . ' h WHERE h.childnodeanchor = :parentNodeAnchorPoint AND h.contentstreamid = :contentStreamId diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index fb81c17eb03..ac615c1131d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -184,8 +184,8 @@ public function findParentNodeAggregates( NodeAggregateId $childNodeAggregateId ): iterable { $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() - ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') - ->innerJoin('ch', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->andWhere('ch.contentstreamid = :contentStreamId') ->andWhere('cn.nodeaggregateid = :nodeAggregateId') ->setParameters([ @@ -210,9 +210,9 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr { $subQueryBuilder = $this->createQueryBuilder() ->select('pn.nodeaggregateid') - ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'pn') - ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->from($this->nodeQueryBuilder->tableNames->node(), 'pn') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('ch.dimensionspacepointhash = :childOriginDimensionSpacePointHash') ->andWhere('cn.nodeaggregateid = :childNodeAggregateId') @@ -220,9 +220,9 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr $queryBuilder = $this->createQueryBuilder() ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->nodeQueryBuilder->contentGraphTableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->from($this->nodeQueryBuilder->tableNames->node(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->nodeQueryBuilder->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') ->andWhere('h.contentstreamid = :contentStreamId') ->setParameters([ @@ -251,10 +251,10 @@ public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeNam { $queryBuilder = $this->createQueryBuilder() ->select('dsp.dimensionspacepoint, h.dimensionspacepointhash') - ->from($this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h') - ->innerJoin('h', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'n', 'n.relationanchorpoint = h.parentnodeanchor') - ->innerJoin('h', $this->nodeQueryBuilder->contentGraphTableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') + ->from($this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h') + ->innerJoin('h', $this->nodeQueryBuilder->tableNames->node(), 'n', 'n.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('h', $this->nodeQueryBuilder->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :parentNodeAggregateId') ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -296,7 +296,7 @@ public function countNodes(): int { $queryBuilder = $this->createQueryBuilder() ->select('COUNT(*)') - ->from($this->nodeQueryBuilder->contentGraphTableNames->node()); + ->from($this->nodeQueryBuilder->tableNames->node()); $result = $queryBuilder->execute(); if (!$result instanceof Result) { throw new \RuntimeException(sprintf('Failed to count nodes. Expected result to be of type %s, got: %s', Result::class, get_debug_type($result)), 1701444550); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index ae3823d73b2..2fe7196ce82 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -253,8 +253,8 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi $queryBuilderInitial = $this->createQueryBuilder() // @see https://mariadb.com/kb/en/library/recursive-common-table-expressions-overview/#cast-to-avoid-data-truncation ->select('n.*, h.name, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') - ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->from($this->nodeQueryBuilder->tableNames->node(), 'n') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); @@ -263,8 +263,8 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi $queryBuilderRecursive = $this->createQueryBuilder() ->select('c.*, h.name, h.subtreetags, p.nodeaggregateid AS parentNodeAggregateId, p.level + 1 AS level, h.position') ->from('tree', 'p') - ->innerJoin('p', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') - ->innerJoin('p', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'c', 'c.relationanchorpoint = h.childnodeanchor') + ->innerJoin('p', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') + ->innerJoin('p', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = h.childnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); if ($filter->maximumLevels !== null) { @@ -345,9 +345,9 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose { $queryBuilderInitial = $this->createQueryBuilder() ->select('n.*, ph.name, ph.subtreetags, ph.parentnodeanchor') - ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') + ->from($this->nodeQueryBuilder->tableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); @@ -356,8 +356,8 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose $queryBuilderRecursive = $this->createQueryBuilder() ->select('pn.*, h.name, h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') - ->innerJoin('cn', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') - ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('cn', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); @@ -473,11 +473,11 @@ private function buildReferencesQuery(bool $backReferences, NodeAggregateId $nod $destinationTablePrefix = $backReferences ? 's' : 'd'; $queryBuilder = $this->createQueryBuilder() ->select("{$destinationTablePrefix}n.*, {$destinationTablePrefix}h.name, {$destinationTablePrefix}h.subtreetags, r.name AS referencename, r.properties AS referenceproperties") - ->from($this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'sh') - ->innerJoin('sh', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') - ->innerJoin('sh', $this->nodeQueryBuilder->contentGraphTableNames->referenceRelation(), 'r', 'r.nodeanchorpoint = sn.relationanchorpoint') - ->innerJoin('sh', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'dn', 'dn.nodeaggregateid = r.destinationnodeaggregateid') - ->innerJoin('sh', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'dh', 'dh.childnodeanchor = dn.relationanchorpoint') + ->from($this->nodeQueryBuilder->tableNames->hierachyRelation(), 'sh') + ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') + ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->referenceRelation(), 'r', 'r.nodeanchorpoint = sn.relationanchorpoint') + ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->node(), 'dn', 'dn.nodeaggregateid = r.destinationnodeaggregateid') + ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'dh', 'dh.childnodeanchor = dn.relationanchorpoint') ->where("{$sourceTablePrefix}n.nodeaggregateid = :nodeAggregateId")->setParameter('nodeAggregateId', $nodeAggregateId->value) ->andWhere('dh.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash') @@ -545,11 +545,11 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId { $queryBuilderInitial = $this->createQueryBuilder() ->select('n.*, ph.name, ph.subtreetags, ph.parentnodeanchor') - ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') + ->from($this->nodeQueryBuilder->tableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') - ->innerJoin('ch', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') - ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -561,8 +561,8 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId $queryBuilderRecursive = $this->createQueryBuilder() ->select('pn.*, h.name, h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') - ->innerJoin('cn', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') - ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('cn', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); @@ -582,11 +582,11 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate $queryBuilderInitial = $this->createQueryBuilder() // @see https://mariadb.com/kb/en/library/recursive-common-table-expressions-overview/#cast-to-avoid-data-truncation ->select('n.*, h.name, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') - ->from($this->nodeQueryBuilder->contentGraphTableNames->node(), 'n') + ->from($this->nodeQueryBuilder->tableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'p', 'p.relationanchorpoint = h.parentnodeanchor') - ->innerJoin('n', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->node(), 'p', 'p.relationanchorpoint = h.parentnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -597,8 +597,8 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate $queryBuilderRecursive = $this->createQueryBuilder() ->select('cn.*, h.name, h.subtreetags, pn.nodeaggregateid AS parentNodeAggregateId, pn.level + 1 AS level, h.position') ->from('tree', 'pn') - ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->nodeQueryBuilder->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php index 5cedb5a21f3..6b7632f46bd 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/DimensionSpacePointsRepository.php @@ -33,7 +33,7 @@ final class DimensionSpacePointsRepository public function __construct( private readonly Connection $databaseConnection, - private readonly ContentGraphTableNames $contentGraphTableNames + private readonly ContentGraphTableNames $tableNames ) { } @@ -82,7 +82,7 @@ public function getOriginDimensionSpacePointByHash(string $hash): OriginDimensio private function writeDimensionSpacePoint(string $hash, string $dimensionSpacePointCoordinatesJson): void { $this->databaseConnection->executeStatement( - 'INSERT IGNORE INTO ' . $this->contentGraphTableNames->dimensionSpacePoints() . ' (hash, dimensionspacepoint) VALUES (:dimensionspacepointhash, :dimensionspacepoint)', + 'INSERT IGNORE INTO ' . $this->tableNames->dimensionSpacePoints() . ' (hash, dimensionspacepoint) VALUES (:dimensionspacepointhash, :dimensionspacepoint)', [ 'dimensionspacepointhash' => $hash, 'dimensionspacepoint' => $dimensionSpacePointCoordinatesJson @@ -97,7 +97,7 @@ private function getCoordinatesByHashFromRuntimeCache(string $hash): ?string private function fillRuntimeCacheFromDatabase(): void { - $allDimensionSpacePoints = $this->databaseConnection->fetchAllAssociative('SELECT hash, dimensionspacepoint FROM ' . $this->contentGraphTableNames->dimensionSpacePoints()); + $allDimensionSpacePoints = $this->databaseConnection->fetchAllAssociative('SELECT hash, dimensionspacepoint FROM ' . $this->tableNames->dimensionSpacePoints()); foreach ($allDimensionSpacePoints as $dimensionSpacePointRow) { $this->dimensionspacePointsRuntimeCache[(string)$dimensionSpacePointRow['hash']] = (string)$dimensionSpacePointRow['dimensionspacepoint']; } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index d4cedfd70ae..a7730a23e4d 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -35,7 +35,7 @@ { public function __construct( private Connection $connection, - public ContentGraphTableNames $contentGraphTableNames + public ContentGraphTableNames $tableNames ) { } @@ -43,9 +43,9 @@ public function buildBasicNodeAggregateQuery(): QueryBuilder { $queryBuilder = $this->createQueryBuilder() ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->contentGraphTableNames->node(), 'n') - ->innerJoin('n', $this->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') - ->innerJoin('h', $this->contentGraphTableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') + ->from($this->tableNames->node(), 'n') + ->innerJoin('n', $this->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('h.contentstreamid = :contentStreamId'); return $queryBuilder; @@ -55,11 +55,11 @@ public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregat { return $this->createQueryBuilder() ->select('cn.*, ch.name, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') - ->from($this->contentGraphTableNames->node(), 'pn') - ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('ch', $this->contentGraphTableNames->dimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') - ->innerJoin('ch', $this->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') + ->from($this->tableNames->node(), 'pn') + ->innerJoin('pn', $this->tableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('ch', $this->tableNames->dimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') + ->innerJoin('ch', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('pn.nodeaggregateid = :parentNodeAggregateId') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ch.contentstreamid = :contentStreamId') @@ -90,8 +90,8 @@ public function buildBasicNodeQuery(ContentStreamId $contentStreamId, DimensionS { return $this->createQueryBuilder() ->select($select) - ->from($this->contentGraphTableNames->node(), $nodeTableAlias) - ->innerJoin($nodeTableAlias, $this->contentGraphTableNames->hierachyRelation(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') + ->from($this->tableNames->node(), $nodeTableAlias) + ->innerJoin($nodeTableAlias, $this->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } @@ -100,9 +100,9 @@ public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId { return $this->createQueryBuilder() ->select('n.*, h.name, h.subtreetags') - ->from($this->contentGraphTableNames->node(), 'pn') - ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->contentGraphTableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') + ->from($this->tableNames->node(), 'pn') + ->innerJoin('pn', $this->tableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); @@ -112,10 +112,10 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, { return $this->createQueryBuilder() ->select('pn.*, ch.name, ch.subtreetags') - ->from($this->contentGraphTableNames->node(), 'pn') - ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->contentGraphTableNames->node(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') - ->innerJoin('pn', $this->contentGraphTableNames->hierachyRelation(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') + ->from($this->tableNames->node(), 'pn') + ->innerJoin('pn', $this->tableNames->hierachyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('pn', $this->tableNames->hierachyRelation(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) ->andWhere('ph.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) ->andWhere('ch.contentstreamid = :contentStreamId') @@ -126,8 +126,8 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { $sharedSubQuery = $this->createQueryBuilder() - ->from($this->contentGraphTableNames->hierachyRelation(), 'sh') - ->innerJoin('sh', $this->contentGraphTableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') + ->from($this->tableNames->hierachyRelation(), 'sh') + ->innerJoin('sh', $this->tableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') ->where('sn.nodeaggregateid = :siblingNodeAggregateId') ->andWhere('sh.contentstreamid = :contentStreamId') ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash'); @@ -257,7 +257,7 @@ public function buildfindUsedNodeTypeNamesQuery(): QueryBuilder { return $this->createQueryBuilder() ->select('DISTINCT nodetypename') - ->from($this->contentGraphTableNames->node()); + ->from($this->tableNames->node()); } private function createQueryBuilder(): QueryBuilder From a208d9fbbbe4c8a95f3e8aaa18ff255b409e4262 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 10:13:31 +0200 Subject: [PATCH 145/214] TASK: Remove dead code from ProjectionContentGraph --- .../Repository/ProjectionContentGraph.php | 106 +----------------- 1 file changed, 2 insertions(+), 104 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index 02e6b63e5b9..51952e36d59 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -30,8 +30,8 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** - * The read only content graph for use by the {@see GraphProjector}. This is the class for low-level operations - * within the projector, where implementation details of the graph structure are known. + * The read only content graph for use by the {@see DoctrineDbalContentGraphProjection}. This is the class for low-level operations + * within the projection, where implementation details of the graph structure are known. * * This is NO PUBLIC API in any way. * @@ -118,35 +118,6 @@ public function findNodeInAggregate( return $nodeRow ? NodeRecord::fromDatabaseRow($nodeRow) : null; } - /** - * @param ContentStreamId $contentStreamId - * @param NodeAggregateId $nodeAggregateId - * @param OriginDimensionSpacePoint $originDimensionSpacePoint - * @return NodeRecord|null - * @throws \Exception - */ - public function findNodeByIds( - ContentStreamId $contentStreamId, - NodeAggregateId $nodeAggregateId, - OriginDimensionSpacePoint $originDimensionSpacePoint - ): ?NodeRecord { - $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT n.*, h.name, h.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON n.origindimensionspacepointhash = dsp.hash - WHERE n.nodeaggregateid = :nodeAggregateId - AND n.origindimensionspacepointhash = :originDimensionSpacePointHash - AND h.contentstreamid = :contentStreamId', - [ - 'contentStreamId' => $contentStreamId->value, - 'nodeAggregateId' => $nodeAggregateId->value, - 'originDimensionSpacePointHash' => $originDimensionSpacePoint->hash - ] - )->fetchAssociative(); - - return $nodeRow ? NodeRecord::fromDatabaseRow($nodeRow) : null; - } - /** * @param NodeAggregateId $nodeAggregateId * @param OriginDimensionSpacePoint $originDimensionSpacePoint @@ -574,79 +545,6 @@ public function getAllContentStreamIdsAnchorPointIsContainedIn( return $contentStreamIds; } - /** - * Finds all descendant node aggregate ids, indexed by dimension space point hash - * - * @param ContentStreamId $contentStreamId - * @param NodeAggregateId $entryNodeAggregateId - * @param DimensionSpacePointSet $affectedDimensionSpacePoints - * @return array|NodeAggregateId[][] - * @throws DBALException - */ - public function findDescendantNodeAggregateIds( - ContentStreamId $contentStreamId, - NodeAggregateId $entryNodeAggregateId, - DimensionSpacePointSet $affectedDimensionSpacePoints - ): array { - $rows = $this->getDatabaseConnection()->executeQuery( - ' - -- ProjectionContentGraph::findDescendantNodeAggregateIds - - WITH RECURSIVE nestedNodes AS ( - -- -------------------------------- - -- INITIAL query: select the root nodes - -- -------------------------------- - SELECT - n.nodeaggregateid, - n.relationanchorpoint, - h.dimensionspacepointhash - FROM - ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h - on h.childnodeanchor = n.relationanchorpoint - WHERE n.nodeaggregateid = :entryNodeAggregateId - AND h.contentstreamid = :contentStreamId - AND h.dimensionspacepointhash IN (:affectedDimensionSpacePointHashes) - - UNION - -- -------------------------------- - -- RECURSIVE query: do one "child" query step - -- -------------------------------- - SELECT - c.nodeaggregateid, - c.relationanchorpoint, - h.dimensionspacepointhash - FROM - nestedNodes p - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h - on h.parentnodeanchor = p.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_node c - on h.childnodeanchor = c.relationanchorpoint - WHERE - h.contentstreamid = :contentStreamId - AND h.dimensionspacepointhash IN (:affectedDimensionSpacePointHashes) - ) - select nodeaggregateid, dimensionspacepointhash from nestedNodes - ', - [ - 'entryNodeAggregateId' => $entryNodeAggregateId->value, - 'contentStreamId' => $contentStreamId->value, - 'affectedDimensionSpacePointHashes' => $affectedDimensionSpacePoints->getPointHashes() - ], - [ - 'affectedDimensionSpacePointHashes' => Connection::PARAM_STR_ARRAY - ] - )->fetchAllAssociative(); - - $nodeAggregateIds = []; - foreach ($rows as $row) { - $nodeAggregateIds[$row['nodeaggregateid']][$row['dimensionspacepointhash']] - = NodeAggregateId::fromString($row['nodeaggregateid']); - } - - return $nodeAggregateIds; - } - /** * @param array $rawData */ From f632cb6268aa6812cc774aaadbd9c29086438979 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 10:13:41 +0200 Subject: [PATCH 146/214] TASK: Use `ContentGraphTableNames` in `ProjectionContentGraph` --- ...trineDbalContentGraphProjectionFactory.php | 2 +- .../Repository/ProjectionContentGraph.php | 59 ++++++++++--------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php index 3ddb6b7a455..402d767abcf 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjectionFactory.php @@ -58,7 +58,7 @@ public function build( $this->dbalClient, new ProjectionContentGraph( $this->dbalClient, - $tableNames->tableNamePrefix + $tableNames ), $tableNames, $dimensionSpacePointsRepository, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index 51952e36d59..17596def501 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -17,6 +17,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver\Exception; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\HierarchyRelation; use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRecord; @@ -41,7 +42,7 @@ class ProjectionContentGraph { public function __construct( private readonly DbalClientInterface $client, - private readonly string $tableNamePrefix + private readonly ContentGraphTableNames $tableNames ) { } @@ -71,11 +72,11 @@ public function findParentNode( : $originDimensionSpacePoint->hash ]; $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT p.*, ph.contentstreamid, ph.name, ph.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node p - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ph ON ph.childnodeanchor = p.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ch ON ch.parentnodeanchor = p.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_node c ON ch.childnodeanchor = c.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON p.origindimensionspacepointhash = dsp.hash + 'SELECT p.*, ph.contentstreamid, ph.name, ph.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNames->node() . ' p + INNER JOIN ' . $this->tableNames->hierachyRelation() . ' ph ON ph.childnodeanchor = p.relationanchorpoint + INNER JOIN ' . $this->tableNames->hierachyRelation() . ' ch ON ch.parentnodeanchor = p.relationanchorpoint + INNER JOIN ' . $this->tableNames->node() . ' c ON ch.childnodeanchor = c.relationanchorpoint + INNER JOIN ' . $this->tableNames->dimensionSpacePoints() . ' dsp ON p.origindimensionspacepointhash = dsp.hash WHERE c.nodeaggregateid = :childNodeAggregateId AND c.origindimensionspacepointhash = :originDimensionSpacePointHash AND ph.contentstreamid = :contentStreamId @@ -102,9 +103,9 @@ public function findNodeInAggregate( DimensionSpacePoint $coveredDimensionSpacePoint ): ?NodeRecord { $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT n.*, h.name, h.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON n.origindimensionspacepointhash = dsp.hash + 'SELECT n.*, h.name, h.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierachyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN ' . $this->tableNames->dimensionSpacePoints() . ' dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -131,8 +132,8 @@ public function getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStrea ContentStreamId $contentStreamId ): ?NodeRelationAnchorPoint { $rows = $this->getDatabaseConnection()->executeQuery( - 'SELECT DISTINCT n.relationanchorpoint FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint + 'SELECT DISTINCT n.relationanchorpoint FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierachyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND n.origindimensionspacepointhash = :originDimensionSpacePointHash AND h.contentstreamid = :contentStreamId', @@ -166,8 +167,8 @@ public function getAnchorPointsForNodeAggregateInContentStream( ContentStreamId $contentStreamId ): iterable { $rows = $this->getDatabaseConnection()->executeQuery( - 'SELECT DISTINCT n.relationanchorpoint FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint + 'SELECT DISTINCT n.relationanchorpoint FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierachyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId', [ @@ -190,8 +191,8 @@ public function getAnchorPointsForNodeAggregateInContentStream( public function getNodeByAnchorPoint(NodeRelationAnchorPoint $nodeRelationAnchorPoint): ?NodeRecord { $nodeRow = $this->getDatabaseConnection()->executeQuery( - 'SELECT n.*, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON n.origindimensionspacepointhash = dsp.hash + 'SELECT n.*, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->dimensionSpacePoints() . ' dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.relationanchorpoint = :relationAnchorPoint', [ 'relationAnchorPoint' => $nodeRelationAnchorPoint->value, @@ -226,7 +227,7 @@ public function determineHierarchyRelationPosition( if ($succeedingSiblingAnchorPoint) { /** @var array $succeedingSiblingRelation */ $succeedingSiblingRelation = $this->getDatabaseConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h WHERE h.childnodeanchor = :succeedingSiblingAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -241,7 +242,7 @@ public function determineHierarchyRelationPosition( $parentAnchorPoint = NodeRelationAnchorPoint::fromInteger($succeedingSiblingRelation['parentnodeanchor']); $precedingSiblingData = $this->getDatabaseConnection()->executeQuery( - 'SELECT MAX(h.position) AS position FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT MAX(h.position) AS position FROM ' . $this->tableNames->hierachyRelation() . ' h WHERE h.parentnodeanchor = :anchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash @@ -267,7 +268,7 @@ public function determineHierarchyRelationPosition( if (!$parentAnchorPoint) { /** @var array $childHierarchyRelationData */ $childHierarchyRelationData = $this->getDatabaseConnection()->executeQuery( - 'SELECT h.parentnodeanchor FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT h.parentnodeanchor FROM ' . $this->tableNames->hierachyRelation() . ' h WHERE h.childnodeanchor = :childAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -282,7 +283,7 @@ public function determineHierarchyRelationPosition( ); } $rightmostSucceedingSiblingRelationData = $this->getDatabaseConnection()->executeQuery( - 'SELECT MAX(h.position) AS position FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT MAX(h.position) AS position FROM ' . $this->tableNames->hierachyRelation() . ' h WHERE h.parentnodeanchor = :parentAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -319,7 +320,7 @@ public function getOutgoingHierarchyRelationsForNodeAndSubgraph( $relations = []; foreach ( $this->getDatabaseConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h WHERE h.parentnodeanchor = :parentAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -351,7 +352,7 @@ public function getIngoingHierarchyRelationsForNodeAndSubgraph( $relations = []; foreach ( $this->getDatabaseConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h WHERE h.childnodeanchor = :childAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -381,7 +382,7 @@ public function findIngoingHierarchyRelationsForNode( DimensionSpacePointSet $restrictToSet = null ): array { $relations = []; - $query = 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + $query = 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h WHERE h.childnodeanchor = :childAnchorPoint AND h.contentstreamid = :contentStreamId'; $parameters = [ @@ -419,7 +420,7 @@ public function findOutgoingHierarchyRelationsForNode( DimensionSpacePointSet $restrictToSet = null ): array { $relations = []; - $query = 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + $query = 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h WHERE h.parentnodeanchor = :parentAnchorPoint AND h.contentstreamid = :contentStreamId'; $parameters = [ @@ -459,8 +460,8 @@ public function findOutgoingHierarchyRelationsForNodeAggregate( $relations = []; foreach ( $this->getDatabaseConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h - INNER JOIN ' . $this->tableNamePrefix . '_node n ON h.parentnodeanchor = n.relationanchorpoint + 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h + INNER JOIN ' . $this->tableNames->node() . ' n ON h.parentnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash IN (:dimensionSpacePointHashes)', @@ -494,8 +495,8 @@ public function findIngoingHierarchyRelationsForNodeAggregate( ): array { $relations = []; - $query = 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h - INNER JOIN ' . $this->tableNamePrefix . '_node n ON h.childnodeanchor = n.relationanchorpoint + $query = 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h + INNER JOIN ' . $this->tableNames->node() . ' n ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId'; $parameters = [ @@ -532,7 +533,7 @@ public function getAllContentStreamIdsAnchorPointIsContainedIn( foreach ( $this->getDatabaseConnection()->executeQuery( 'SELECT DISTINCT h.contentstreamid - FROM ' . $this->tableNamePrefix . '_hierarchyrelation h + FROM ' . $this->tableNames->hierachyRelation() . ' h WHERE h.childnodeanchor = :nodeRelationAnchorPoint', [ 'nodeRelationAnchorPoint' => $nodeRelationAnchorPoint->value, @@ -550,7 +551,7 @@ public function getAllContentStreamIdsAnchorPointIsContainedIn( */ protected function mapRawDataToHierarchyRelation(array $rawData): HierarchyRelation { - $dimensionspacepointRaw = $this->client->getConnection()->fetchOne('SELECT dimensionspacepoint FROM ' . $this->tableNamePrefix . '_dimensionspacepoints WHERE hash = :hash', ['hash' => $rawData['dimensionspacepointhash']]); + $dimensionspacepointRaw = $this->client->getConnection()->fetchOne('SELECT dimensionspacepoint FROM ' . $this->tableNames->dimensionSpacePoints() . ' WHERE hash = :hash', ['hash' => $rawData['dimensionspacepointhash']]); return new HierarchyRelation( NodeRelationAnchorPoint::fromInteger((int)$rawData['parentnodeanchor']), From fb3121298b40c546445d2bfdf58bcb192b8380bf Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 10:15:33 +0200 Subject: [PATCH 147/214] TASK: Fix typo in `ContentGraphTableNames::hierarchyRelation` --- .../src/ContentGraphTableNames.php | 2 +- .../DoctrineDbalContentGraphProjection.php | 26 +++++++-------- .../DoctrineDbalContentGraphSchemaBuilder.php | 2 +- .../Domain/Projection/Feature/NodeRemoval.php | 2 +- .../Projection/Feature/SubtreeTagging.php | 28 ++++++++-------- .../Domain/Projection/HierarchyRelation.php | 10 +++--- .../src/Domain/Repository/ContentGraph.php | 10 +++--- .../src/Domain/Repository/ContentSubgraph.php | 24 +++++++------- .../Repository/ProjectionContentGraph.php | 32 +++++++++---------- .../src/NodeQueryBuilder.php | 16 +++++----- 10 files changed, 76 insertions(+), 76 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php index 175eb07be52..438a4ec0e19 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -25,7 +25,7 @@ public function node(): string return $this->tableNamePrefix . '_node'; } - public function hierachyRelation(): string + public function hierarchyRelation(): string { return $this->tableNamePrefix . '_hierarchyrelation'; } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php index 0d67b426ef1..d97e80194c7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php @@ -154,7 +154,7 @@ private function truncateDatabaseTables(): void { $connection = $this->dbalClient->getConnection(); $connection->executeQuery('TRUNCATE table ' . $this->tableNames->node()); - $connection->executeQuery('TRUNCATE table ' . $this->tableNames->hierachyRelation()); + $connection->executeQuery('TRUNCATE table ' . $this->tableNames->hierarchyRelation()); $connection->executeQuery('TRUNCATE table ' . $this->tableNames->referenceRelation()); $connection->executeQuery('TRUNCATE table ' . $this->tableNames->dimensionSpacePoints()); } @@ -273,7 +273,7 @@ private function whenRootNodeAggregateDimensionsWereUpdated(RootNodeAggregateDim $this->transactional(function () use ($rootNodeAnchorPoint, $event) { // delete all hierarchy edges of the root node $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->tableNames->hierachyRelation() . ' + DELETE FROM ' . $this->tableNames->hierarchyRelation() . ' WHERE parentnodeanchor = :parentNodeAnchor AND childnodeanchor = :childNodeAnchor @@ -322,7 +322,7 @@ private function whenNodeAggregateNameWasChanged(NodeAggregateNameWasChanged $ev { $this->transactional(function () use ($event, $eventEnvelope) { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->tableNames->hierachyRelation() . ' h + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h INNER JOIN ' . $this->tableNames->node() . ' n on h.childnodeanchor = n.relationanchorpoint SET @@ -559,7 +559,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void // 1) Copy HIERARCHY RELATIONS (this is the MAIN OPERATION here) // $this->getDatabaseConnection()->executeUpdate(' - INSERT INTO ' . $this->tableNames->hierachyRelation() . ' ( + INSERT INTO ' . $this->tableNames->hierarchyRelation() . ' ( parentnodeanchor, childnodeanchor, `name`, @@ -577,7 +577,7 @@ private function whenContentStreamWasForked(ContentStreamWasForked $event): void h.subtreetags, "' . $event->newContentStreamId->value . '" AS contentstreamid FROM - ' . $this->tableNames->hierachyRelation() . ' h + ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.contentstreamid = :sourceContentStreamId ', [ 'sourceContentStreamId' => $event->sourceContentStreamId->value @@ -594,7 +594,7 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo // Drop hierarchy relations $this->getDatabaseConnection()->executeUpdate(' - DELETE FROM ' . $this->tableNames->hierachyRelation() . ' + DELETE FROM ' . $this->tableNames->hierarchyRelation() . ' WHERE contentstreamid = :contentStreamId ', [ @@ -606,8 +606,8 @@ private function whenContentStreamWasRemoved(ContentStreamWasRemoved $event): vo DELETE FROM ' . $this->tableNames->node() . ' WHERE NOT EXISTS ( - SELECT 1 FROM ' . $this->tableNames->hierachyRelation() . ' - WHERE ' . $this->tableNames->hierachyRelation() . '.childnodeanchor + SELECT 1 FROM ' . $this->tableNames->hierarchyRelation() . ' + WHERE ' . $this->tableNames->hierarchyRelation() . '.childnodeanchor = ' . $this->tableNames->node() . '.relationanchorpoint ) '); @@ -832,7 +832,7 @@ private function updateNodeRecordWithCopyOnWrite( // IMPORTANT: We need to reconnect BOTH the incoming and outgoing edges. $this->getDatabaseConnection()->executeStatement( ' - UPDATE ' . $this->tableNames->hierachyRelation() . ' h + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h SET -- if our (copied) node is the child, we update h.childNodeAnchor h.childnodeanchor @@ -908,7 +908,7 @@ private function whenDimensionSpacePointWasMoved(DimensionSpacePointWasMoved $ev $rel = $this->getDatabaseConnection()->executeQuery( 'SELECT n.relationanchorpoint, n.origindimensionspacepointhash FROM ' . $this->tableNames->node() . ' n - INNER JOIN ' . $this->tableNames->hierachyRelation() . ' h + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint AND h.contentstreamid = :contentStreamId @@ -937,7 +937,7 @@ function (NodeRecord $nodeRecord) use ($event) { // 2) hierarchy relations $this->getDatabaseConnection()->executeStatement( ' - UPDATE ' . $this->tableNames->hierachyRelation() . ' h + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h SET h.dimensionspacepointhash = :newDimensionSpacePointHash WHERE @@ -961,7 +961,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded // 1) hierarchy relations $this->getDatabaseConnection()->executeStatement( ' - INSERT INTO ' . $this->tableNames->hierachyRelation() . ' ( + INSERT INTO ' . $this->tableNames->hierarchyRelation() . ' ( parentnodeanchor, childnodeanchor, `name`, @@ -979,7 +979,7 @@ private function whenDimensionShineThroughWasAdded(DimensionShineThroughWasAdded :newDimensionSpacePointHash AS dimensionspacepointhash, h.contentstreamid FROM - ' . $this->tableNames->hierachyRelation() . ' h + ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :sourceDimensionSpacePointHash', [ diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php index eae660dc35a..c8520724f01 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphSchemaBuilder.php @@ -55,7 +55,7 @@ private function createNodeTable(): Table private function createHierarchyRelationTable(): Table { - $table = new Table($this->contentGraphTableNames->hierachyRelation(), [ + $table = new Table($this->contentGraphTableNames->hierarchyRelation(), [ (new Column('name', Type::getType(Types::STRING)))->setLength(255)->setNotnull(false)->setCustomSchemaOption('charset', 'ascii')->setCustomSchemaOption('collation', 'ascii_general_ci'), (new Column('position', Type::getType(Types::INTEGER)))->setNotnull(true), DbalSchemaFactory::columnForContentStreamId('contentstreamid')->setNotnull(true), diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php index 96012716631..41f00af02fa 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/NodeRemoval.php @@ -69,7 +69,7 @@ protected function removeRelationRecursivelyFromDatabaseIncludingNonReferencedNo LEFT JOIN ' . $this->tableNames->referenceRelation() . ' r ON r.nodeanchorpoint = n.relationanchorpoint LEFT JOIN - ' . $this->tableNames->hierachyRelation() . ' h + ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE n.relationanchorpoint = :anchorPointForNode diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php index 1373303a5b1..2564a0a36ef 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/Feature/SubtreeTagging.php @@ -27,12 +27,12 @@ trait SubtreeTagging private function whenSubtreeWasTagged(SubtreeWasTagged $event): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->tableNames->hierachyRelation() . ' h + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h SET h.subtreetags = JSON_INSERT(h.subtreetags, :tagPath, null) WHERE h.childnodeanchor IN ( WITH RECURSIVE cte (id) AS ( SELECT ch.childnodeanchor - FROM ' . $this->tableNames->hierachyRelation() . ' ch + FROM ' . $this->tableNames->hierarchyRelation() . ' ch INNER JOIN ' . $this->tableNames->node() . ' n ON n.relationanchorpoint = ch.parentnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId @@ -44,7 +44,7 @@ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void dh.childnodeanchor FROM cte - JOIN ' . $this->tableNames->hierachyRelation() . ' dh ON dh.parentnodeanchor = cte.id + JOIN ' . $this->tableNames->hierarchyRelation() . ' dh ON dh.parentnodeanchor = cte.id WHERE NOT JSON_CONTAINS_PATH(dh.subtreetags, \'one\', :tagPath) ) @@ -62,7 +62,7 @@ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void ]); $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->tableNames->hierachyRelation() . ' h + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h INNER JOIN ' . $this->tableNames->node() . ' n ON n.relationanchorpoint = h.childnodeanchor SET h.subtreetags = JSON_SET(h.subtreetags, :tagPath, true) WHERE @@ -85,14 +85,14 @@ private function whenSubtreeWasTagged(SubtreeWasTagged $event): void private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->tableNames->hierachyRelation() . ' h - INNER JOIN ' . $this->tableNames->hierachyRelation() . ' ph ON ph.childnodeanchor = h.parentnodeanchor + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' ph ON ph.childnodeanchor = h.parentnodeanchor SET h.subtreetags = IF(( SELECT JSON_CONTAINS_PATH(ph.subtreetags, \'one\', :tagPath) FROM - ' . $this->tableNames->hierachyRelation() . ' ph - INNER JOIN ' . $this->tableNames->hierachyRelation() . ' ch ON ch.parentnodeanchor = ph.childnodeanchor + ' . $this->tableNames->hierarchyRelation() . ' ph + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' ch ON ch.parentnodeanchor = ph.childnodeanchor INNER JOIN ' . $this->tableNames->node() . ' n ON n.relationanchorpoint = ch.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId @@ -103,7 +103,7 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void WHERE h.childnodeanchor IN ( WITH RECURSIVE cte (id) AS ( SELECT ch.childnodeanchor - FROM ' . $this->tableNames->hierachyRelation() . ' ch + FROM ' . $this->tableNames->hierarchyRelation() . ' ch INNER JOIN ' . $this->tableNames->node() . ' n ON n.relationanchorpoint = ch.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId @@ -114,7 +114,7 @@ private function whenSubtreeWasUntagged(SubtreeWasUntagged $event): void dh.childnodeanchor FROM cte - JOIN ' . $this->tableNames->hierachyRelation() . ' dh ON dh.parentnodeanchor = cte.id + JOIN ' . $this->tableNames->hierarchyRelation() . ' dh ON dh.parentnodeanchor = cte.id WHERE JSON_EXTRACT(dh.subtreetags, :tagPath) != TRUE ) @@ -138,13 +138,13 @@ private function moveSubtreeTags( DimensionSpacePoint $coveredDimensionSpacePoint ): void { $this->getDatabaseConnection()->executeStatement(' - UPDATE ' . $this->tableNames->hierachyRelation() . ' h, + UPDATE ' . $this->tableNames->hierarchyRelation() . ' h, ( WITH RECURSIVE cte AS ( SELECT JSON_KEYS(th.subtreetags) subtreeTagsToInherit, th.childnodeanchor FROM - ' . $this->tableNames->hierachyRelation() . ' th + ' . $this->tableNames->hierarchyRelation() . ' th INNER JOIN ' . $this->tableNames->node() . ' tn ON tn.relationanchorpoint = th.childnodeanchor WHERE tn.nodeaggregateid = :newParentNodeAggregateId @@ -162,7 +162,7 @@ private function moveSubtreeTags( dh.childnodeanchor FROM cte - JOIN ' . $this->tableNames->hierachyRelation() . ' dh + JOIN ' . $this->tableNames->hierarchyRelation() . ' dh ON dh.parentnodeanchor = cte.childnodeanchor AND dh.contentstreamid = :contentStreamId @@ -196,7 +196,7 @@ private function subtreeTagsForHierarchyRelation(ContentStreamId $contentStreamI return NodeTags::createEmpty(); } $subtreeTagsJson = $this->getDatabaseConnection()->fetchOne(' - SELECT h.subtreetags FROM ' . $this->tableNames->hierachyRelation() . ' h + SELECT h.subtreetags FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :parentNodeAnchorPoint AND h.contentstreamid = :contentStreamId diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php index fe621a3a4d5..f829f87a936 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/HierarchyRelation.php @@ -52,7 +52,7 @@ public function addToDatabase(Connection $databaseConnection, ContentGraphTableN $dimensionSpacePoints = new DimensionSpacePointsRepository($databaseConnection, $tableNames); $dimensionSpacePoints->insertDimensionSpacePoint($this->dimensionSpacePoint); - $databaseConnection->insert($tableNames->hierachyRelation(), [ + $databaseConnection->insert($tableNames->hierarchyRelation(), [ 'parentnodeanchor' => $this->parentNodeAnchor->value, 'childnodeanchor' => $this->childNodeAnchor->value, 'name' => $this->name?->value, @@ -69,7 +69,7 @@ public function addToDatabase(Connection $databaseConnection, ContentGraphTableN */ public function removeFromDatabase(Connection $databaseConnection, ContentGraphTableNames $tableNames): void { - $databaseConnection->delete($tableNames->hierachyRelation(), $this->getDatabaseId()); + $databaseConnection->delete($tableNames->hierarchyRelation(), $this->getDatabaseId()); } /** @@ -82,7 +82,7 @@ public function assignNewChildNode( ContentGraphTableNames $tableNames ): void { $databaseConnection->update( - $tableNames->hierachyRelation(), + $tableNames->hierarchyRelation(), [ 'childnodeanchor' => $childAnchorPoint->value ], @@ -106,7 +106,7 @@ public function assignNewParentNode( $data['position'] = $position; } $databaseConnection->update( - $tableNames->hierachyRelation(), + $tableNames->hierarchyRelation(), $data, $this->getDatabaseId() ); @@ -115,7 +115,7 @@ public function assignNewParentNode( public function assignNewPosition(int $position, Connection $databaseConnection, ContentGraphTableNames $tableNames): void { $databaseConnection->update( - $tableNames->hierachyRelation(), + $tableNames->hierarchyRelation(), [ 'position' => $position ], diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index ac615c1131d..9273b36a956 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -184,7 +184,7 @@ public function findParentNodeAggregates( NodeAggregateId $childNodeAggregateId ): iterable { $queryBuilder = $this->nodeQueryBuilder->buildBasicNodeAggregateQuery() - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->andWhere('ch.contentstreamid = :contentStreamId') ->andWhere('cn.nodeaggregateid = :nodeAggregateId') @@ -211,7 +211,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr $subQueryBuilder = $this->createQueryBuilder() ->select('pn.nodeaggregateid') ->from($this->nodeQueryBuilder->tableNames->node(), 'pn') - ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('ch.dimensionspacepointhash = :childOriginDimensionSpacePointHash') @@ -221,7 +221,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr $queryBuilder = $this->createQueryBuilder() ->select('n.*, h.name, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->nodeQueryBuilder->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('n.nodeaggregateid = (' . $subQueryBuilder->getSQL() . ')') ->andWhere('h.contentstreamid = :contentStreamId') @@ -251,10 +251,10 @@ public function getDimensionSpacePointsOccupiedByChildNodeName(NodeName $nodeNam { $queryBuilder = $this->createQueryBuilder() ->select('dsp.dimensionspacepoint, h.dimensionspacepointhash') - ->from($this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h') + ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h') ->innerJoin('h', $this->nodeQueryBuilder->tableNames->node(), 'n', 'n.relationanchorpoint = h.parentnodeanchor') ->innerJoin('h', $this->nodeQueryBuilder->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = n.relationanchorpoint') ->where('n.nodeaggregateid = :parentNodeAggregateId') ->andWhere('n.origindimensionspacepointhash = :parentNodeOriginDimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index 2fe7196ce82..a8c43d988aa 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -254,7 +254,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi // @see https://mariadb.com/kb/en/library/recursive-common-table-expressions-overview/#cast-to-avoid-data-truncation ->select('n.*, h.name, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); @@ -263,7 +263,7 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi $queryBuilderRecursive = $this->createQueryBuilder() ->select('c.*, h.name, h.subtreetags, p.nodeaggregateid AS parentNodeAggregateId, p.level + 1 AS level, h.position') ->from('tree', 'p') - ->innerJoin('p', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') + ->innerJoin('p', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = p.relationanchorpoint') ->innerJoin('p', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = h.childnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); @@ -347,7 +347,7 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose ->select('n.*, ph.name, ph.subtreetags, ph.parentnodeanchor') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->andWhere('ph.contentstreamid = :contentStreamId') ->andWhere('ph.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('n.nodeaggregateid = :entryNodeAggregateId'); @@ -357,7 +357,7 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose ->select('pn.*, h.name, h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') ->innerJoin('cn', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') - ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); @@ -473,11 +473,11 @@ private function buildReferencesQuery(bool $backReferences, NodeAggregateId $nod $destinationTablePrefix = $backReferences ? 's' : 'd'; $queryBuilder = $this->createQueryBuilder() ->select("{$destinationTablePrefix}n.*, {$destinationTablePrefix}h.name, {$destinationTablePrefix}h.subtreetags, r.name AS referencename, r.properties AS referenceproperties") - ->from($this->nodeQueryBuilder->tableNames->hierachyRelation(), 'sh') + ->from($this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'sh') ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->referenceRelation(), 'r', 'r.nodeanchorpoint = sn.relationanchorpoint') ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->node(), 'dn', 'dn.nodeaggregateid = r.destinationnodeaggregateid') - ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'dh', 'dh.childnodeanchor = dn.relationanchorpoint') + ->innerJoin('sh', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'dh', 'dh.childnodeanchor = dn.relationanchorpoint') ->where("{$sourceTablePrefix}n.nodeaggregateid = :nodeAggregateId")->setParameter('nodeAggregateId', $nodeAggregateId->value) ->andWhere('dh.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $this->dimensionSpacePoint->hash) ->andWhere('sh.dimensionspacepointhash = :dimensionSpacePointHash') @@ -547,9 +547,9 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId ->select('n.*, ph.name, ph.subtreetags, ph.parentnodeanchor') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = n.relationanchorpoint') ->innerJoin('ch', $this->nodeQueryBuilder->tableNames->node(), 'c', 'c.relationanchorpoint = ch.childnodeanchor') - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'n.relationanchorpoint = ph.childnodeanchor') ->where('ch.contentstreamid = :contentStreamId') ->andWhere('ch.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -562,7 +562,7 @@ private function buildAncestorNodesQueries(NodeAggregateId $entryNodeAggregateId ->select('pn.*, h.name, h.subtreetags, h.parentnodeanchor') ->from('ancestry', 'cn') ->innerJoin('cn', $this->nodeQueryBuilder->tableNames->node(), 'pn', 'pn.relationanchorpoint = cn.parentnodeanchor') - ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = pn.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); $this->addSubtreeTagConstraints($queryBuilderRecursive); @@ -584,9 +584,9 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate ->select('n.*, h.name, h.subtreetags, CAST("ROOT" AS CHAR(50)) AS parentNodeAggregateId, 0 AS level, 0 AS position') ->from($this->nodeQueryBuilder->tableNames->node(), 'n') // we need to join with the hierarchy relation, because we need the node name. - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('n', $this->nodeQueryBuilder->tableNames->node(), 'p', 'p.relationanchorpoint = h.parentnodeanchor') - ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') + ->innerJoin('n', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = p.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash') ->andWhere('ph.contentstreamid = :contentStreamId') @@ -597,7 +597,7 @@ private function buildDescendantNodesQueries(NodeAggregateId $entryNodeAggregate $queryBuilderRecursive = $this->createQueryBuilder() ->select('cn.*, h.name, h.subtreetags, pn.nodeaggregateid AS parentNodeAggregateId, pn.level + 1 AS level, h.position') ->from('tree', 'pn') - ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->nodeQueryBuilder->tableNames->node(), 'cn', 'cn.relationanchorpoint = h.childnodeanchor') ->where('h.contentstreamid = :contentStreamId') ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash'); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php index 17596def501..f41e6591b88 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ProjectionContentGraph.php @@ -73,8 +73,8 @@ public function findParentNode( ]; $nodeRow = $this->getDatabaseConnection()->executeQuery( 'SELECT p.*, ph.contentstreamid, ph.name, ph.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNames->node() . ' p - INNER JOIN ' . $this->tableNames->hierachyRelation() . ' ph ON ph.childnodeanchor = p.relationanchorpoint - INNER JOIN ' . $this->tableNames->hierachyRelation() . ' ch ON ch.parentnodeanchor = p.relationanchorpoint + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' ph ON ph.childnodeanchor = p.relationanchorpoint + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' ch ON ch.parentnodeanchor = p.relationanchorpoint INNER JOIN ' . $this->tableNames->node() . ' c ON ch.childnodeanchor = c.relationanchorpoint INNER JOIN ' . $this->tableNames->dimensionSpacePoints() . ' dsp ON p.origindimensionspacepointhash = dsp.hash WHERE c.nodeaggregateid = :childNodeAggregateId @@ -104,7 +104,7 @@ public function findNodeInAggregate( ): ?NodeRecord { $nodeRow = $this->getDatabaseConnection()->executeQuery( 'SELECT n.*, h.name, h.subtreetags, dsp.dimensionspacepoint AS origindimensionspacepoint FROM ' . $this->tableNames->node() . ' n - INNER JOIN ' . $this->tableNames->hierachyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint INNER JOIN ' . $this->tableNames->dimensionSpacePoints() . ' dsp ON n.origindimensionspacepointhash = dsp.hash WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId @@ -133,7 +133,7 @@ public function getAnchorPointForNodeAndOriginDimensionSpacePointAndContentStrea ): ?NodeRelationAnchorPoint { $rows = $this->getDatabaseConnection()->executeQuery( 'SELECT DISTINCT n.relationanchorpoint FROM ' . $this->tableNames->node() . ' n - INNER JOIN ' . $this->tableNames->hierachyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND n.origindimensionspacepointhash = :originDimensionSpacePointHash AND h.contentstreamid = :contentStreamId', @@ -168,7 +168,7 @@ public function getAnchorPointsForNodeAggregateInContentStream( ): iterable { $rows = $this->getDatabaseConnection()->executeQuery( 'SELECT DISTINCT n.relationanchorpoint FROM ' . $this->tableNames->node() . ' n - INNER JOIN ' . $this->tableNames->hierachyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId', [ @@ -227,7 +227,7 @@ public function determineHierarchyRelationPosition( if ($succeedingSiblingAnchorPoint) { /** @var array $succeedingSiblingRelation */ $succeedingSiblingRelation = $this->getDatabaseConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h + 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :succeedingSiblingAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -242,7 +242,7 @@ public function determineHierarchyRelationPosition( $parentAnchorPoint = NodeRelationAnchorPoint::fromInteger($succeedingSiblingRelation['parentnodeanchor']); $precedingSiblingData = $this->getDatabaseConnection()->executeQuery( - 'SELECT MAX(h.position) AS position FROM ' . $this->tableNames->hierachyRelation() . ' h + 'SELECT MAX(h.position) AS position FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.parentnodeanchor = :anchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash @@ -268,7 +268,7 @@ public function determineHierarchyRelationPosition( if (!$parentAnchorPoint) { /** @var array $childHierarchyRelationData */ $childHierarchyRelationData = $this->getDatabaseConnection()->executeQuery( - 'SELECT h.parentnodeanchor FROM ' . $this->tableNames->hierachyRelation() . ' h + 'SELECT h.parentnodeanchor FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :childAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -283,7 +283,7 @@ public function determineHierarchyRelationPosition( ); } $rightmostSucceedingSiblingRelationData = $this->getDatabaseConnection()->executeQuery( - 'SELECT MAX(h.position) AS position FROM ' . $this->tableNames->hierachyRelation() . ' h + 'SELECT MAX(h.position) AS position FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.parentnodeanchor = :parentAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -320,7 +320,7 @@ public function getOutgoingHierarchyRelationsForNodeAndSubgraph( $relations = []; foreach ( $this->getDatabaseConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h + 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.parentnodeanchor = :parentAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -352,7 +352,7 @@ public function getIngoingHierarchyRelationsForNodeAndSubgraph( $relations = []; foreach ( $this->getDatabaseConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h + 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :childAnchorPoint AND h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash', @@ -382,7 +382,7 @@ public function findIngoingHierarchyRelationsForNode( DimensionSpacePointSet $restrictToSet = null ): array { $relations = []; - $query = 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h + $query = 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :childAnchorPoint AND h.contentstreamid = :contentStreamId'; $parameters = [ @@ -420,7 +420,7 @@ public function findOutgoingHierarchyRelationsForNode( DimensionSpacePointSet $restrictToSet = null ): array { $relations = []; - $query = 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h + $query = 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.parentnodeanchor = :parentAnchorPoint AND h.contentstreamid = :contentStreamId'; $parameters = [ @@ -460,7 +460,7 @@ public function findOutgoingHierarchyRelationsForNodeAggregate( $relations = []; foreach ( $this->getDatabaseConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h + 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h INNER JOIN ' . $this->tableNames->node() . ' n ON h.parentnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId @@ -495,7 +495,7 @@ public function findIngoingHierarchyRelationsForNodeAggregate( ): array { $relations = []; - $query = 'SELECT h.* FROM ' . $this->tableNames->hierachyRelation() . ' h + $query = 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h INNER JOIN ' . $this->tableNames->node() . ' n ON h.childnodeanchor = n.relationanchorpoint WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId'; @@ -533,7 +533,7 @@ public function getAllContentStreamIdsAnchorPointIsContainedIn( foreach ( $this->getDatabaseConnection()->executeQuery( 'SELECT DISTINCT h.contentstreamid - FROM ' . $this->tableNames->hierachyRelation() . ' h + FROM ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.childnodeanchor = :nodeRelationAnchorPoint', [ 'nodeRelationAnchorPoint' => $nodeRelationAnchorPoint->value, diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index a7730a23e4d..9fd82b24be9 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -44,7 +44,7 @@ public function buildBasicNodeAggregateQuery(): QueryBuilder $queryBuilder = $this->createQueryBuilder() ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNames->node(), 'n') - ->innerJoin('n', $this->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') + ->innerJoin('n', $this->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') ->where('h.contentstreamid = :contentStreamId'); @@ -56,8 +56,8 @@ public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregat return $this->createQueryBuilder() ->select('cn.*, ch.name, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNames->node(), 'pn') - ->innerJoin('pn', $this->tableNames->hierachyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') - ->innerJoin('pn', $this->tableNames->hierachyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('ch', $this->tableNames->dimensionSpacePoints(), 'cdsp', 'cdsp.hash = ch.dimensionspacepointhash') ->innerJoin('ch', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ch.childnodeanchor') ->where('pn.nodeaggregateid = :parentNodeAggregateId') @@ -91,7 +91,7 @@ public function buildBasicNodeQuery(ContentStreamId $contentStreamId, DimensionS return $this->createQueryBuilder() ->select($select) ->from($this->tableNames->node(), $nodeTableAlias) - ->innerJoin($nodeTableAlias, $this->tableNames->hierachyRelation(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') + ->innerJoin($nodeTableAlias, $this->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = ' . $nodeTableAlias . '.relationanchorpoint') ->where('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) ->andWhere('h.dimensionspacepointhash = :dimensionSpacePointHash')->setParameter('dimensionSpacePointHash', $dimensionSpacePoint->hash); } @@ -101,7 +101,7 @@ public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId return $this->createQueryBuilder() ->select('n.*, h.name, h.subtreetags') ->from($this->tableNames->node(), 'pn') - ->innerJoin('pn', $this->tableNames->hierachyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') ->where('pn.nodeaggregateid = :parentNodeAggregateId')->setParameter('parentNodeAggregateId', $parentNodeAggregateId->value) ->andWhere('h.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) @@ -113,9 +113,9 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, return $this->createQueryBuilder() ->select('pn.*, ch.name, ch.subtreetags') ->from($this->tableNames->node(), 'pn') - ->innerJoin('pn', $this->tableNames->hierachyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') - ->innerJoin('pn', $this->tableNames->hierachyRelation(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') + ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ch', 'ch.childnodeanchor = pn.relationanchorpoint') ->where('cn.nodeaggregateid = :childNodeAggregateId')->setParameter('childNodeAggregateId', $childNodeAggregateId->value) ->andWhere('ph.contentstreamid = :contentStreamId')->setParameter('contentStreamId', $contentStreamId->value) ->andWhere('ch.contentstreamid = :contentStreamId') @@ -126,7 +126,7 @@ public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, public function buildBasicNodeSiblingsQuery(bool $preceding, NodeAggregateId $siblingNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { $sharedSubQuery = $this->createQueryBuilder() - ->from($this->tableNames->hierachyRelation(), 'sh') + ->from($this->tableNames->hierarchyRelation(), 'sh') ->innerJoin('sh', $this->tableNames->node(), 'sn', 'sn.relationanchorpoint = sh.childnodeanchor') ->where('sn.nodeaggregateid = :siblingNodeAggregateId') ->andWhere('sh.contentstreamid = :contentStreamId') From 8f6c36f51c257eff15fc882ec4d5938b5aff5f7b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 10:25:01 +0200 Subject: [PATCH 148/214] TASK: Adjust fusion inline documentation to `hiddenInMenu` --- .../Classes/Fusion/AbstractMenuItemsImplementation.php | 2 +- .../Documentation/References/NeosFusionReference.rst | 8 ++++---- .../Private/Fusion/Prototypes/BreadcrumbMenu.fusion | 2 +- .../Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion | 2 +- Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion | 2 +- .../Resources/Private/Fusion/Prototypes/MenuItems.fusion | 2 +- Neos.Neos/Resources/Private/Styles/_Tree.scss | 1 + 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php index a30e63316f0..c984480b5a8 100644 --- a/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php @@ -24,7 +24,7 @@ * Base class for Menu and DimensionsMenu * * Main Options: - * - renderHiddenInMenu: if TRUE, hidden-in-index nodes will be shown in the menu. FALSE by default. + * - renderHiddenInMenu: if TRUE, nodes with the property ``hiddenInMenu`` will be shown in the menu. FALSE by default. */ abstract class AbstractMenuItemsImplementation extends AbstractFusionObject { diff --git a/Neos.Neos/Documentation/References/NeosFusionReference.rst b/Neos.Neos/Documentation/References/NeosFusionReference.rst index ecaa0e08f6f..99db1ded359 100644 --- a/Neos.Neos/Documentation/References/NeosFusionReference.rst +++ b/Neos.Neos/Documentation/References/NeosFusionReference.rst @@ -927,7 +927,7 @@ The following properties are passed over to :ref:`Neos_Neos__MenuItems` internal :maximumLevels: (integer) Restrict the maximum depth of items in the menu (relative to ``entryLevel``) :startingPoint: (optional, Node) The node where the menu hierarchy starts. If not specified explicitly the startingPoint is calculated from (``node`` and ``entryLevel``), defaults to ``null`` :filter: (string) Filter items by node type (e.g. ``'!My.Site:News,Neos.Neos:Document'``), defaults to ``'Neos.Neos:Document'``. The filter is only used for fetching subItems and is ignored for determining the ``startingPoint`` -:renderHiddenInMenu: (boolean) Whether nodes with ``hiddenInIndex`` should be rendered, defaults to ``false`` +:renderHiddenInMenu: (boolean) Whether nodes with the property ``hiddenInMenu`` should be rendered, defaults to ``false`` :calculateItemStates: (boolean) activate the *expensive* calculation of item states defaults to ``false``. :itemCollection: (optional, array of Nodes) Explicitly set the Node items for the menu (taking precedence over ``startingPoints`` and ``entryLevel`` and ``lastLevel``). The children for each ``Node`` will be fetched taking the ``maximumLevels`` property into account. @@ -957,7 +957,7 @@ The following properties are passed over to :ref:`Neos_Neos__BreadcrumbMenuItems :node: (Node) The current node to render the menu for. Defaults to ``documentNode`` from the fusion context :maximumLevels: (integer) Restrict the maximum depth of items in the menu, defaults to ``0`` -:renderHiddenInMenu: (boolean) Whether nodes with ``hiddenInIndex`` should be rendered (the current documentNode is always included), defaults to ``false``. +:renderHiddenInMenu: (boolean) Whether nodes with the property ``hiddenInMenu`` should be rendered (the current documentNode is always included), defaults to ``false``. :calculateItemStates: (boolean) activate the *expensive* calculation of item states defaults to ``false`` Example:: @@ -1017,7 +1017,7 @@ Create a list of menu-items items for nodes. :maximumLevels: (integer) Restrict the maximum depth of items in the menu (relative to ``entryLevel``) :startingPoint: (optional, Node) The node where the menu hierarchy starts. If not specified explicitly the startingPoint is calculated from (``node`` and ``entryLevel``), defaults to ``null`` :filter: (string) Filter items by node type (e.g. ``'!My.Site:News,Neos.Neos:Document'``), defaults to ``'Neos.Neos:Document'``. The filter is only used for fetching subItems and is ignored for determining the ``startingPoint`` -:renderHiddenInMenu: (boolean) Whether nodes with ``hiddenInIndex`` should be rendered, defaults to ``false`` +:renderHiddenInMenu: (boolean) Whether nodes with the property ``hiddenInMenu`` should be rendered, defaults to ``false`` :calculateItemStates: (boolean) activate the *expensive* calculation of item states defaults to ``false``. :itemCollection: (optional, array of Nodes) Explicitly set the Node items for the menu (taking precedence over ``startingPoints`` and ``entryLevel`` and ``lastLevel``). The children for each ``Node`` will be fetched taking the ``maximumLevels`` property into account. @@ -1084,7 +1084,7 @@ Create a list of of menu-items for the breadcrumb (ancestor documents). :node: (Node) The current node to render the menu for. Defaults to ``documentNode`` from the fusion context :maximumLevels: (integer) Restrict the maximum depth of items in the menu, defaults to ``0`` -:renderHiddenInMenu: (boolean) Whether nodes with ``hiddenInIndex`` should be rendered (the current documentNode is always included), defaults to ``false``. +:renderHiddenInMenu: (boolean) Whether nodes with the property ``hiddenInMenu`` should be rendered (the current documentNode is always included), defaults to ``false``. :calculateItemStates: (boolean) activate the *expensive* calculation of item states defaults to ``false`` Example:: diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion index 0da25707ac1..efcc7fec79f 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion @@ -11,7 +11,7 @@ prototype(Neos.Neos:BreadcrumbMenu) < prototype(Neos.Fusion:Component) { # (integer) Restrict the maximum depth of items in the menu, defaults to ``0`` maximumLevels = 0 - # (boolean) Whether nodes with ``hiddenInIndex`` should be rendered (the current documentNode is always included), defaults to ``false``. + # (boolean) Whether nodes with the property ``hiddenInMenu`` should be rendered (the current documentNode is always included), defaults to ``false``. renderHiddenInIndex = true # (boolean) activate the *expensive* calculation of item states defaults to ``false`` diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion index 8b128ba4eb8..6c5cc2dc182 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion @@ -6,7 +6,7 @@ prototype(Neos.Neos:BreadcrumbMenuItems) < prototype(Neos.Fusion:Component) { # (integer) Restrict the maximum depth of items in the menu, defaults to ``0`` maximumLevels = 0 - # (boolean) Whether nodes with ``hiddenInIndex`` should be rendered (the current documentNode is always included), defaults to ``false``. + # (boolean) Whether nodes with the property ``hiddenInMenu`` should be rendered (the current documentNode is always included), defaults to ``false``. renderHiddenInIndex = true # (boolean) activate the *expensive* calculation of item states defaults to ``false`` diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion index c435eb45e3c..1f896a826dc 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion @@ -22,7 +22,7 @@ prototype(Neos.Neos:Menu) < prototype(Neos.Fusion:Component) { # (string) Filter items by node type (e.g. ``'!My.Site:News,Neos.Neos:Document'``), defaults to ``'Neos.Neos:Document'``. The filter is only used for fetching subItems and is ignored for determining the ``startingPoint`` filter = 'Neos.Neos:Document' - # (boolean) Whether nodes with ``hiddenInIndex`` should be rendered, defaults to ``false`` + # (boolean) Whether nodes with the property ``hiddenInMenu`` should be rendered, defaults to ``false`` renderHiddenInIndex = false # (boolean) activate the *expensive* calculation of item states defaults to ``false``. diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/MenuItems.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/MenuItems.fusion index edf6bc37c60..b8b5202ca57 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/MenuItems.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/MenuItems.fusion @@ -19,7 +19,7 @@ prototype(Neos.Neos:MenuItems) { # (string) Filter items by node type (e.g. ``'!My.Site:News,Neos.Neos:Document'``), defaults to ``'Neos.Neos:Document'``. The filter is only used for fetching subItems and is ignored for determining the ``startingPoint`` filter = 'Neos.Neos:Document' - # (boolean) Whether nodes with ``hiddenInIndex`` should be rendered, defaults to ``false`` + # (boolean) Whether nodes with the property ``hiddenInMenu`` should be rendered, defaults to ``false`` renderHiddenInIndex = false # (boolean) activate the *expensive* calculation of item states defaults to ``false``. diff --git a/Neos.Neos/Resources/Private/Styles/_Tree.scss b/Neos.Neos/Resources/Private/Styles/_Tree.scss index bdf01001691..ee25d121a8b 100644 --- a/Neos.Neos/Resources/Private/Styles/_Tree.scss +++ b/Neos.Neos/Resources/Private/Styles/_Tree.scss @@ -54,6 +54,7 @@ ul.neos-tree-container { } } + /* todo legacy, should now be named hiddenInMenu */ &.neos-hiddenInIndex { span + span { opacity: 0.5; From 019b29ed055d023c51fdc3c994da5b5cb75b45a0 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 10:27:24 +0200 Subject: [PATCH 149/214] TASK: Adjust fusion dimension menu documentation to `hiddenInMenu` --- Neos.Neos/Documentation/References/NeosFusionReference.rst | 2 +- .../Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion | 2 +- .../Private/Fusion/Prototypes/DimensionsMenuItems.fusion | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Neos.Neos/Documentation/References/NeosFusionReference.rst b/Neos.Neos/Documentation/References/NeosFusionReference.rst index 99db1ded359..bc26a54be82 100644 --- a/Neos.Neos/Documentation/References/NeosFusionReference.rst +++ b/Neos.Neos/Documentation/References/NeosFusionReference.rst @@ -986,7 +986,7 @@ The following fusion properties are passed over to :ref:`Neos_Neos__DimensionsMe :dimension: (optional, string): name of the dimension which this menu should be based on. Example: "language". :presets: (optional, array): If set, the presets rendered will be taken from this list of preset identifiers :includeAllPresets: (boolean, default **false**) If TRUE, include all presets, not only allowed combinations -:renderHiddenInMenu: (boolean, default **true**) If TRUE, render nodes which are marked as "hidded-in-index" +:renderHiddenInMenu: (boolean, default **true**) Whether nodes with the property ``hiddenInMenu`` should be rendered :calculateItemStates: (boolean) activate the *expensive* calculation of item states defaults to ``false`` .. note:: The ``items`` of the ``DimensionsMenu`` are internally calculated with the prototype :ref:`Neos_Neos__DimensionsMenuItems` which diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion index d5a75fb9ff6..bb61cdd769f 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion @@ -6,7 +6,7 @@ prototype(Neos.Neos:DimensionsMenu) < prototype(Neos.Fusion:Component) { # html attributes for the rendered list attributes = Neos.Fusion:DataStructure - # (boolean, default **true**) If TRUE, render nodes which are marked as "hidded-in-index" + # (boolean, default **true**) Whether nodes with the property ``hiddenInMenu`` should be rendered renderHiddenInIndex = true # (optional, string): name of the dimension which this menu should be based on. Example: "language". diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenuItems.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenuItems.fusion index 55629907ada..bc7c3822055 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenuItems.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenuItems.fusion @@ -4,7 +4,7 @@ prototype(Neos.Neos:DimensionsMenuItems) { # (Node) The current node. Defaults to ``node`` from the fusion context node = ${documentNode} - # (boolean, default **true**) If TRUE, render nodes which are marked as "hidded-in-index" + # (boolean, default **true**) Whether nodes with the property ``hiddenInMenu`` should be rendered renderHiddenInIndex = true # (optional, string): name of the dimension which this menu should be based on. Example: "language". From ab7a4d79dd4a1ef31f9ab6bad9cda00700fc33c4 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 10:29:00 +0200 Subject: [PATCH 150/214] TASK: Adjust fusion default value declaration to use `renderHiddenInIndex` --- .../Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion | 2 +- .../Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion | 4 ++-- .../Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion | 2 +- .../Private/Fusion/Prototypes/DimensionsMenuItems.fusion | 2 +- Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion | 2 +- .../Resources/Private/Fusion/Prototypes/MenuItems.fusion | 2 +- Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion index efcc7fec79f..fea328190b8 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion @@ -12,7 +12,7 @@ prototype(Neos.Neos:BreadcrumbMenu) < prototype(Neos.Fusion:Component) { maximumLevels = 0 # (boolean) Whether nodes with the property ``hiddenInMenu`` should be rendered (the current documentNode is always included), defaults to ``false``. - renderHiddenInIndex = true + renderHiddenInMenu = true # (boolean) activate the *expensive* calculation of item states defaults to ``false`` calculateItemStates = false diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion index 6c5cc2dc182..646f2f04938 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion @@ -7,7 +7,7 @@ prototype(Neos.Neos:BreadcrumbMenuItems) < prototype(Neos.Fusion:Component) { maximumLevels = 0 # (boolean) Whether nodes with the property ``hiddenInMenu`` should be rendered (the current documentNode is always included), defaults to ``false``. - renderHiddenInIndex = true + renderHiddenInMenu = true # (boolean) activate the *expensive* calculation of item states defaults to ``false`` calculateItemStates = false @@ -24,7 +24,7 @@ prototype(Neos.Neos:BreadcrumbMenuItems) < prototype(Neos.Fusion:Component) { currentItem = Neos.Neos:MenuItems { node = ${props.node} calculateItemStates = ${props.calculateItemStates} - renderHiddenInIndex = true + renderHiddenInMenu = true maximumLevels = ${props.maximumLevels} itemCollection = ${[documentNode]} } diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion index bb61cdd769f..e8150f3a315 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion @@ -7,7 +7,7 @@ prototype(Neos.Neos:DimensionsMenu) < prototype(Neos.Fusion:Component) { attributes = Neos.Fusion:DataStructure # (boolean, default **true**) Whether nodes with the property ``hiddenInMenu`` should be rendered - renderHiddenInIndex = true + renderHiddenInMenu = true # (optional, string): name of the dimension which this menu should be based on. Example: "language". dimension = null diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenuItems.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenuItems.fusion index bc7c3822055..d45f3d09083 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenuItems.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenuItems.fusion @@ -5,7 +5,7 @@ prototype(Neos.Neos:DimensionsMenuItems) { node = ${documentNode} # (boolean, default **true**) Whether nodes with the property ``hiddenInMenu`` should be rendered - renderHiddenInIndex = true + renderHiddenInMenu = true # (optional, string): name of the dimension which this menu should be based on. Example: "language". dimension = null diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion index 1f896a826dc..cecb93848db 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion @@ -23,7 +23,7 @@ prototype(Neos.Neos:Menu) < prototype(Neos.Fusion:Component) { filter = 'Neos.Neos:Document' # (boolean) Whether nodes with the property ``hiddenInMenu`` should be rendered, defaults to ``false`` - renderHiddenInIndex = false + renderHiddenInMenu = false # (boolean) activate the *expensive* calculation of item states defaults to ``false``. calculateItemStates = false diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/MenuItems.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/MenuItems.fusion index b8b5202ca57..c11b9762844 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/MenuItems.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/MenuItems.fusion @@ -20,7 +20,7 @@ prototype(Neos.Neos:MenuItems) { filter = 'Neos.Neos:Document' # (boolean) Whether nodes with the property ``hiddenInMenu`` should be rendered, defaults to ``false`` - renderHiddenInIndex = false + renderHiddenInMenu = false # (boolean) activate the *expensive* calculation of item states defaults to ``false``. calculateItemStates = false diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature index 541e1c5444f..2e4935ae410 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature @@ -469,13 +469,13 @@ Feature: Tests for the "Neos.Neos:Menu" and related Fusion prototypes """ - Scenario: MenuItems (startingPoint a1c, renderHiddenInIndex) + Scenario: MenuItems (startingPoint a1c, renderHiddenInMenu) When I execute the following Fusion code: """fusion test = Neos.Neos:Test.Menu { items = Neos.Neos:MenuItems { startingPoint = ${q(node).find('#a1c').get(0)} - renderHiddenInIndex = true + renderHiddenInMenu = true } } """ From 3c1f5f5eafdf12db468294324fc1d8935472d067 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 10:29:16 +0200 Subject: [PATCH 151/214] TASK: Remove obsolete constants from `AbstractMenuItemsImplementation` --- Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php index c984480b5a8..42c84415c97 100644 --- a/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php @@ -28,11 +28,6 @@ */ abstract class AbstractMenuItemsImplementation extends AbstractFusionObject { - public const STATE_NORMAL = 'normal'; - public const STATE_CURRENT = 'current'; - public const STATE_ACTIVE = 'active'; - public const STATE_ABSENT = 'absent'; - /** * An internal cache for the built menu items array. * From 36a913d1a0f62ddb9e33094c98eb5a4139313437 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 10:30:56 +0200 Subject: [PATCH 152/214] TASK: Adjust passed fusion properties to `renderHiddenInMenu` --- .../Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion | 2 +- .../Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion | 2 +- .../Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion | 2 +- Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion index fea328190b8..2c0be2b2649 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenu.fusion @@ -21,7 +21,7 @@ prototype(Neos.Neos:BreadcrumbMenu) < prototype(Neos.Fusion:Component) { items = Neos.Neos:BreadcrumbMenuItems { node = ${props.node} maximumLevels = ${props.maximumLevels} - renderHiddenInIndex = ${props.renderHiddenInIndex} + renderHiddenInMenu = ${props.renderHiddenInMenu} calculateItemStates = ${props.calculateItemStates} } } diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion index 646f2f04938..1fe593d93d5 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/BreadcrumbMenuItems.fusion @@ -16,7 +16,7 @@ prototype(Neos.Neos:BreadcrumbMenuItems) < prototype(Neos.Fusion:Component) { parentItems = Neos.Neos:MenuItems { node = ${props.node} calculateItemStates = ${props.calculateItemStates} - renderHiddenInIndex = ${props.renderHiddenInIndex} + renderHiddenInMenu = ${props.renderHiddenInMenu} maximumLevels = ${props.maximumLevels} itemCollection = ${Array.reverse(q(documentNode).parents('[instanceof Neos.Neos:Document]').get())} } diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion index e8150f3a315..599746ba283 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/DimensionsMenu.fusion @@ -24,7 +24,7 @@ prototype(Neos.Neos:DimensionsMenu) < prototype(Neos.Fusion:Component) { @private { items = Neos.Neos:DimensionsMenuItems { node = ${props.node} - renderHiddenInIndex = ${props.renderHiddenInIndex} + renderHiddenInMenu = ${props.renderHiddenInMenu} dimension = ${props.dimension} presets = ${props.presets} includeAllPresets = ${props.includeAllPresets} diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion index cecb93848db..4c0e00360da 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/Menu.fusion @@ -40,7 +40,7 @@ prototype(Neos.Neos:Menu) < prototype(Neos.Fusion:Component) { maximumLevels = ${props.maximumLevels} startingPoint = ${props.startingPoint} filter = ${props.filter} - renderHiddenInIndex = ${props.renderHiddenInIndex} + renderHiddenInMenu = ${props.renderHiddenInMenu} calculateItemStates = ${props.calculateItemStates} itemCollection = ${props.itemCollection} } From 71609dd085069dfabf75246453b21169dcfe8a5d Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 10:32:48 +0200 Subject: [PATCH 153/214] TASK: Remove fallback layer for `renderHiddenInIndex` in Fusion --- .../Classes/Fusion/AbstractMenuItemsImplementation.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php index 42c84415c97..7595c4f060e 100644 --- a/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/AbstractMenuItemsImplementation.php @@ -72,13 +72,11 @@ public function isCalculateItemStatesEnabled(): bool /** * Should nodes that have "hiddenInMenu" set still be visible in this menu. - * - * @return boolean */ - public function getRenderHiddenInMenu() + public function getRenderHiddenInMenu(): bool { if ($this->renderHiddenInMenu === null) { - $this->renderHiddenInMenu = (bool)($this->fusionValue('renderHiddenInMenu') ?? $this->fusionValue('renderHiddenInIndex')); + $this->renderHiddenInMenu = (bool)$this->fusionValue('renderHiddenInMenu'); } return $this->renderHiddenInMenu; From 9e6fcf0322f871ece50da2749315a033ceb80b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 10 May 2024 11:12:17 +0200 Subject: [PATCH 154/214] Remove unused and unuseful getContentGraph methods --- .../Classes/CommandHandlingDependencies.php | 10 ++++++++++ .../NodeDuplication/NodeDuplicationCommandHandler.php | 5 ----- .../src/Adjustment/TetheredNodeAdjustments.php | 6 ------ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php index 42f23458d33..9df36f5d2ad 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php @@ -1,5 +1,15 @@ getContentGraph($workspaceName); - } - protected function getNodeTypeManager(): NodeTypeManager { return $this->nodeTypeManager; diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index 469da6ec48d..e80974464f0 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -254,10 +254,4 @@ private function reorderNodes( ExpectedVersion::ANY() ); } - - protected function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface - { - return $this->contentRepository->getContentGraph($workspaceName); - } - } From e045cea55637c4880097da94c312626986f04faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 10 May 2024 12:20:51 +0200 Subject: [PATCH 155/214] Use TableNames in ProjectionIntegrityViolationDetector --- ...tegrityViolationDetectionRunnerFactory.php | 2 +- .../ProjectionIntegrityViolationDetector.php | 87 ++++++++++--------- 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory.php index a77588faffc..2daf3c6a5b9 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory.php @@ -27,7 +27,7 @@ public function build( $this->dbalClient, ContentGraphTableNames::create( $serviceFactoryDependencies->contentRepositoryId - )->tableNamePrefix + ) ) ); } diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php index adb394fb6cd..2e99276d62b 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/ProjectionIntegrityViolationDetector.php @@ -14,6 +14,7 @@ namespace Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection; +use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; @@ -33,7 +34,7 @@ final class ProjectionIntegrityViolationDetector implements ProjectionIntegrityV { public function __construct( private readonly DbalClientInterface $client, - private readonly string $tableNamePrefix + private readonly ContentGraphTableNames $tableNames ) { } @@ -45,9 +46,9 @@ public function hierarchyIntegrityIsProvided(): Result $result = new Result(); $disconnectedHierarchyRelationRecords = $this->client->getConnection()->executeQuery( - 'SELECT h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h - LEFT JOIN ' . $this->tableNamePrefix . '_node p ON h.parentnodeanchor = p.relationanchorpoint - LEFT JOIN ' . $this->tableNamePrefix . '_node c ON h.childnodeanchor = c.relationanchorpoint + 'SELECT h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h + LEFT JOIN ' . $this->tableNames->node() . ' p ON h.parentnodeanchor = p.relationanchorpoint + LEFT JOIN ' . $this->tableNames->node() . ' c ON h.childnodeanchor = c.relationanchorpoint WHERE h.parentnodeanchor != :rootNodeAnchor AND ( p.relationanchorpoint IS NULL @@ -67,7 +68,7 @@ public function hierarchyIntegrityIsProvided(): Result } $invalidlyHashedHierarchyRelationRecords = $this->client->getConnection()->executeQuery( - 'SELECT * FROM ' . $this->tableNamePrefix . '_hierarchyrelation h LEFT JOIN ' . $this->tableNamePrefix . '_dimensionspacepoints dsp ON dsp.hash = h.dimensionspacepointhash + 'SELECT * FROM ' . $this->tableNames->hierarchyRelation() . ' h LEFT JOIN ' . $this->tableNames->dimensionSpacePoints() . ' dsp ON dsp.hash = h.dimensionspacepointhash HAVING dsp.dimensionspacepoint IS NULL' )->fetchAllAssociative(); @@ -80,9 +81,9 @@ public function hierarchyIntegrityIsProvided(): Result } $hierarchyRelationRecordsAppearingMultipleTimes = $this->client->getConnection()->executeQuery( - 'SELECT COUNT(*) as uniquenessCounter, h.* FROM ' . $this->tableNamePrefix . '_hierarchyrelation h - LEFT JOIN ' . $this->tableNamePrefix . '_node p ON h.parentnodeanchor = p.relationanchorpoint - LEFT JOIN ' . $this->tableNamePrefix . '_node c ON h.childnodeanchor = c.relationanchorpoint + 'SELECT COUNT(*) as uniquenessCounter, h.* FROM ' . $this->tableNames->hierarchyRelation() . ' h + LEFT JOIN ' . $this->tableNames->node() . ' p ON h.parentnodeanchor = p.relationanchorpoint + LEFT JOIN ' . $this->tableNames->node() . ' c ON h.childnodeanchor = c.relationanchorpoint WHERE h.parentnodeanchor != :rootNodeAnchor GROUP BY p.nodeaggregateid, c.nodeaggregateid, h.dimensionspacepointhash, h.contentstreamid @@ -113,7 +114,7 @@ public function siblingsAreDistinctlySorted(): Result $ambiguouslySortedHierarchyRelationRecords = $this->client->getConnection()->executeQuery( 'SELECT *, COUNT(position) - FROM ' . $this->tableNamePrefix . '_hierarchyrelation + FROM ' . $this->tableNames->hierarchyRelation() . ' GROUP BY position, parentnodeanchor, contentstreamid, dimensionspacepointhash HAVING COUNT(position) > 1' ); @@ -127,7 +128,7 @@ public function siblingsAreDistinctlySorted(): Result foreach ($ambiguouslySortedHierarchyRelationRecords as $hierarchyRelationRecord) { $ambiguouslySortedNodeRecords = $this->client->getConnection()->executeQuery( 'SELECT nodeaggregateid - FROM ' . $this->tableNamePrefix . '_node + FROM ' . $this->tableNames->node() . ' WHERE relationanchorpoint = :relationAnchorPoint', [ 'relationAnchorPoint' => $hierarchyRelationRecord['childnodeanchor'] @@ -155,8 +156,8 @@ public function tetheredNodesAreNamed(): Result $result = new Result(); $unnamedTetheredNodeRecords = $this->client->getConnection()->executeQuery( 'SELECT n.nodeaggregateid, h.contentstreamid - FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE n.classification = :tethered AND h.name IS NULL @@ -192,8 +193,8 @@ public function subtreeTagsAreInherited(): Result 'SELECT ph.name FROM - ' . $this->tableNamePrefix . '_hierarchyrelation h - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation ph + ' . $this->tableNames->hierarchyRelation() . ' h + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' ph ON ph.childnodeanchor = h.parentnodeanchor AND ph.contentstreamid = h.contentstreamid AND ph.dimensionspacepointhash = h.dimensionspacepointhash @@ -222,9 +223,9 @@ public function referenceIntegrityIsProvided(): Result $result = new Result(); $referenceRelationRecordsDetachedFromSource = $this->client->getConnection()->executeQuery( - 'SELECT * FROM ' . $this->tableNamePrefix . '_referencerelation + 'SELECT * FROM ' . $this->tableNames->referenceRelation() . ' WHERE nodeanchorpoint NOT IN ( - SELECT relationanchorpoint FROM ' . $this->tableNamePrefix . '_node + SELECT relationanchorpoint FROM ' . $this->tableNames->node() . ' )' )->fetchAllAssociative(); @@ -240,13 +241,13 @@ public function referenceIntegrityIsProvided(): Result 'SELECT sh.contentstreamid AS contentstreamId, s.nodeaggregateid AS sourceNodeAggregateId, r.destinationnodeaggregateid AS destinationNodeAggregateId - FROM ' . $this->tableNamePrefix . '_referencerelation r - INNER JOIN ' . $this->tableNamePrefix . '_node s ON r.nodeanchorpoint = s.relationanchorpoint - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation sh + FROM ' . $this->tableNames->referenceRelation() . ' r + INNER JOIN ' . $this->tableNames->node() . ' s ON r.nodeanchorpoint = s.relationanchorpoint + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' sh ON r.nodeanchorpoint = sh.childnodeanchor LEFT JOIN ( - ' . $this->tableNamePrefix . '_node d - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation dh + ' . $this->tableNames->node() . ' d + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' dh ON d.relationanchorpoint = dh.childnodeanchor ) ON r.destinationnodeaggregateid = d.nodeaggregateid AND sh.contentstreamid = dh.contentstreamid @@ -291,7 +292,7 @@ public function allNodesAreConnectedToARootNodePerSubgraph(): Result SELECT h.childnodeanchor FROM - ' . $this->tableNamePrefix . '_hierarchyrelation h + ' . $this->tableNames->hierarchyRelation() . ' h WHERE h.parentnodeanchor = :rootAnchorPoint AND h.contentstreamid = :contentStreamId @@ -304,14 +305,14 @@ public function allNodesAreConnectedToARootNodePerSubgraph(): Result h.childnodeanchor FROM subgraph p - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h on h.parentnodeanchor = p.childnodeanchor WHERE h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash ) -SELECT nodeaggregateid FROM ' . $this->tableNamePrefix . '_node n -INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h +SELECT nodeaggregateid FROM ' . $this->tableNames->node() . ' n +INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE h.contentstreamid = :contentStreamId @@ -364,8 +365,8 @@ public function nodeAggregateIdsAreUniquePerSubgraph(): Result foreach ($this->findProjectedDimensionSpacePoints() as $dimensionSpacePoint) { $ambiguousNodeAggregateRecords = $this->client->getConnection()->executeQuery( 'SELECT n.nodeaggregateid, COUNT(n.relationanchorpoint) - FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash @@ -401,8 +402,8 @@ public function allNodesHaveAtMostOneParentPerSubgraph(): Result foreach ($this->findProjectedDimensionSpacePoints() as $dimensionSpacePoint) { $nodeRecordsWithMultipleParents = $this->client->getConnection()->executeQuery( 'SELECT c.nodeaggregateid - FROM ' . $this->tableNamePrefix . '_node c - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + FROM ' . $this->tableNames->node() . ' c + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = c.relationanchorpoint WHERE h.contentstreamid = :contentStreamId AND h.dimensionspacepointhash = :dimensionSpacePointHash @@ -441,8 +442,8 @@ public function nodeAggregatesAreConsistentlyTypedPerContentStream(): Result ) as $nodeAggregateId ) { $nodeAggregateRecords = $this->client->getConnection()->executeQuery( - 'SELECT DISTINCT n.nodetypename FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT DISTINCT n.nodetypename FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE h.contentstreamid = :contentStreamId AND n.nodeaggregateid = :nodeAggregateId', @@ -484,8 +485,8 @@ public function nodeAggregatesAreConsistentlyClassifiedPerContentStream(): Resul ) as $nodeAggregateId ) { $nodeAggregateRecords = $this->client->getConnection()->executeQuery( - 'SELECT DISTINCT n.classification FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + 'SELECT DISTINCT n.classification FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE h.contentstreamid = :contentStreamId AND n.nodeaggregateid = :nodeAggregateId', @@ -523,10 +524,10 @@ public function childNodeCoverageIsASubsetOfParentNodeCoverage(): Result foreach ($this->findProjectedContentStreamIds() as $contentStreamId) { $excessivelyCoveringNodeRecords = $this->client->getConnection()->executeQuery( 'SELECT n.nodeaggregateid, c.dimensionspacepointhash - FROM ' . $this->tableNamePrefix . '_hierarchyrelation c - INNER JOIN ' . $this->tableNamePrefix . '_node n + FROM ' . $this->tableNames->hierarchyRelation() . ' c + INNER JOIN ' . $this->tableNames->node() . ' n ON c.childnodeanchor = n.relationanchorpoint - LEFT JOIN ' . $this->tableNamePrefix . '_hierarchyrelation p + LEFT JOIN ' . $this->tableNames->hierarchyRelation() . ' p ON c.parentnodeanchor = p.childnodeanchor WHERE c.contentstreamid = :contentStreamId AND p.contentstreamid = :contentStreamId @@ -560,16 +561,16 @@ public function allNodesCoverTheirOrigin(): Result foreach ($this->findProjectedContentStreamIds() as $contentStreamId) { $nodeRecordsWithMissingOriginCoverage = $this->client->getConnection()->executeQuery( 'SELECT nodeaggregateid, origindimensionspacepointhash - FROM ' . $this->tableNamePrefix . '_node n - INNER JOIN ' . $this->tableNamePrefix . '_hierarchyrelation h + FROM ' . $this->tableNames->node() . ' n + INNER JOIN ' . $this->tableNames->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE h.contentstreamid = :contentStreamId AND nodeaggregateid NOT IN ( -- this query finds all nodes whose origin *IS COVERED* by an incoming hierarchy relation. SELECT n.nodeaggregateid - FROM ' . $this->tableNamePrefix . '_node n - LEFT JOIN ' . $this->tableNamePrefix . '_hierarchyrelation p + FROM ' . $this->tableNames->node() . ' n + LEFT JOIN ' . $this->tableNames->hierarchyRelation() . ' p ON p.childnodeanchor = n.relationanchorpoint AND p.dimensionspacepointhash = n.origindimensionspacepointhash WHERE p.contentstreamid = :contentStreamId @@ -605,7 +606,7 @@ protected function findProjectedContentStreamIds(): iterable $connection = $this->client->getConnection(); $rows = $connection->executeQuery( - 'SELECT DISTINCT contentstreamid FROM ' . $this->tableNamePrefix . '_hierarchyrelation' + 'SELECT DISTINCT contentstreamid FROM ' . $this->tableNames->hierarchyRelation() )->fetchAllAssociative(); return array_map(function (array $row) { @@ -621,7 +622,7 @@ protected function findProjectedContentStreamIds(): iterable protected function findProjectedDimensionSpacePoints(): DimensionSpacePointSet { $records = $this->client->getConnection()->executeQuery( - 'SELECT dimensionspacepoint FROM ' . $this->tableNamePrefix . '_dimensionspacepoints' + 'SELECT dimensionspacepoint FROM ' . $this->tableNames->dimensionSpacePoints() )->fetchAllAssociative(); $records = array_map(function (array $record) { @@ -639,7 +640,7 @@ protected function findProjectedNodeAggregateIdsInContentStream( ContentStreamId $contentStreamId ): array { $records = $this->client->getConnection()->executeQuery( - 'SELECT DISTINCT nodeaggregateid FROM ' . $this->tableNamePrefix . '_node' + 'SELECT DISTINCT nodeaggregateid FROM ' . $this->tableNames->node() )->fetchAllAssociative(); return array_map(function (array $record) { From cf56d8bb24680bdcc5a77ef1b608ff028490bd62 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 13:40:17 +0200 Subject: [PATCH 156/214] TASK: Throw `WorkspaceDoesNotExist` exception when calling `getContentGraph` --- .../src/ContentGraphFactory.php | 4 +- .../Classes/ContentGraphFactoryInterface.php | 4 ++ .../Classes/ContentGraphFinder.php | 3 +- .../Classes/ContentRepository.php | 4 ++ .../Feature/WorkspaceCommandHandler.php | 41 ++++++++----------- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php index de24f71f1aa..66ae7720899 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphFactory.php @@ -18,7 +18,7 @@ use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; -use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -58,7 +58,7 @@ public function buildForWorkspace(WorkspaceName $workspaceName): ContentGraph )->fetchAssociative(); if ($row === false) { - throw new ContentStreamDoesNotExistYet('The workspace "' . $workspaceName->value . '" does not exist.', 1714839710); + throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); } return $this->buildForWorkspaceAndContentStream($workspaceName, ContentStreamId::fromString($row['currentcontentstreamid'])); diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php b/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php index bfb68749484..2a766579751 100644 --- a/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php +++ b/Neos.ContentRepository.Core/Classes/ContentGraphFactoryInterface.php @@ -15,6 +15,7 @@ namespace Neos\ContentRepository\Core; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -25,6 +26,9 @@ */ interface ContentGraphFactoryInterface { + /** + * @throws WorkspaceDoesNotExist if the workspace does not exist + */ public function buildForWorkspace(WorkspaceName $workspaceName): ContentGraphInterface; public function buildForWorkspaceAndContentStream(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface; diff --git a/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php b/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php index 04bd5fac1de..8244c2e0640 100644 --- a/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php +++ b/Neos.ContentRepository.Core/Classes/ContentGraphFinder.php @@ -45,8 +45,7 @@ public function __construct( * The default way to get a content graph to operate on. * The currently assigned ContentStreamId for the given Workspace is resolved internally. * - * @throws WorkspaceDoesNotExist if there is no workspace with the provided name - * @throws ContentStreamDoesNotExistYet if the provided workspace does not resolve to an existing content stream + * @throws WorkspaceDoesNotExist if the provided workspace does not resolve to an existing content stream * @see ContentRepository::getContentGraph() */ public function getByWorkspaceName(WorkspaceName $workspaceName): ContentGraphInterface diff --git a/Neos.ContentRepository.Core/Classes/ContentRepository.php b/Neos.ContentRepository.Core/Classes/ContentRepository.php index 3c54a2735ed..f6fc32cbc0e 100644 --- a/Neos.ContentRepository.Core/Classes/ContentRepository.php +++ b/Neos.ContentRepository.Core/Classes/ContentRepository.php @@ -38,6 +38,7 @@ use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryStatus; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\User\UserIdProviderInterface; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\EventStore\EventStoreInterface; @@ -238,6 +239,9 @@ public function getNodeTypeManager(): NodeTypeManager return $this->nodeTypeManager; } + /** + * @throws WorkspaceDoesNotExist if the workspace does not exist + */ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface { return $this->projectionState(ContentGraphFinder::class)->getByWorkspaceName($workspaceName); diff --git a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php index 4ebdadd94d0..d4de04228a2 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/WorkspaceCommandHandler.php @@ -126,18 +126,7 @@ private function handleCreateWorkspace( CreateWorkspace $command, CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - try { - $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); - $contentStreamId = $contentGraph->getContentStreamId(); - } catch (ContentStreamDoesNotExistYet $e) { - // Desired outcome - } - - isset($contentStreamId) - && throw new WorkspaceAlreadyExists(sprintf( - 'The workspace %s already exists', - $command->workspaceName->value - ), 1505830958921); + $this->requireWorkspaceToNotExist($command->workspaceName, $commandHandlingDependencies); $baseWorkspace = $commandHandlingDependencies->getWorkspaceFinder()->findOneByName($command->baseWorkspaceName); if ($baseWorkspace === null) { @@ -209,18 +198,7 @@ private function handleCreateRootWorkspace( CreateRootWorkspace $command, CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { - try { - $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); - $contentStreamId = $contentGraph->getContentStreamId(); - } catch (ContentStreamDoesNotExistYet $e) { - // Desired outcome - } - - isset($contentStreamId) - && throw new WorkspaceAlreadyExists(sprintf( - 'The workspace %s already exists', - $command->workspaceName->value - ), 1505848624450); + $this->requireWorkspaceToNotExist($command->workspaceName, $commandHandlingDependencies); $newContentStreamId = $command->newContentStreamId; $commandHandlingDependencies->handle( @@ -917,6 +895,21 @@ private function handleChangeWorkspaceOwner( ); } + private function requireWorkspaceToNotExist(WorkspaceName $workspaceName, CommandHandlingDependencies $commandHandlingDependencies): void + { + try { + $commandHandlingDependencies->getContentGraph($workspaceName); + } catch (WorkspaceDoesNotExist) { + // Desired outcome + return; + } + + throw new WorkspaceAlreadyExists(sprintf( + 'The workspace %s already exists', + $workspaceName->value + ), 1715341085); + } + /** * @throws WorkspaceDoesNotExist */ From 19b5104589ab9dd56a20cb6f7c79b31383812a8a Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 15:40:56 +0200 Subject: [PATCH 157/214] TASK: Adjust executed exception to `WorkspaceDoesNotExist` in tests --- ...1-CreateRootNodeAggregateWithNode_ConstraintChecks.feature | 2 +- .../01-CreateNodeAggregateWithNode_ConstraintChecks.feature | 2 +- .../01-CreateNodeVariant_ConstraintChecks.feature | 2 +- .../01-SetNodeProperties_ConstraintChecks.feature | 2 +- .../01-SetNodeReferences_ConstraintChecks.feature | 2 +- .../01-DisableNodeAggregate_ConstraintChecks.feature | 2 +- .../04-EnableNodeAggregate_ConstraintChecks.feature | 2 +- .../01-RemoveNodeAggregate_ConstraintChecks.feature | 2 +- .../08-NodeMove/01-MoveNodes_ConstraintChecks.feature | 2 +- .../01_ChangeNodeAggregateName_ConstraintChecks.feature | 2 +- .../ChangeNodeAggregateType_BasicErrorCases.feature | 2 +- .../Classes/CommandHandlingDependencies.php | 4 ++++ 12 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/01-CreateRootNodeAggregateWithNode_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/01-CreateRootNodeAggregateWithNode_ConstraintChecks.feature index b1ff005357c..d7c4a1716df 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/01-CreateRootNodeAggregateWithNode_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/01-CreateRootNodeAggregateWithNode_ConstraintChecks.feature @@ -40,7 +40,7 @@ Feature: Create a root node aggregate | workspaceName | "i-do-not-exist" | | nodeAggregateId | "nody-mc-nodeface" | | nodeTypeName | "Neos.ContentRepository:Root" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to create a root node aggregate in a closed content stream: When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature index 7ca3ef355a9..da3862003de 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature @@ -54,7 +54,7 @@ Feature: Create node aggregate with node | parentNodeAggregateId | "lady-eleonode-rootford" | | nodeName | "document" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to create a node aggregate in a workspace whose content stream is closed: When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/01-CreateNodeVariant_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/01-CreateNodeVariant_ConstraintChecks.feature index d4d2be10e79..2105bfd50e9 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/01-CreateNodeVariant_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/01-CreateNodeVariant_ConstraintChecks.feature @@ -48,7 +48,7 @@ Feature: Create node variant | nodeAggregateId | "sir-david-nodenborough" | | sourceOrigin | {"market":"CH", "language":"gsw"} | | targetOrigin | {"market":"DE", "language":"de"} | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to create a variant in a workspace that does not exist When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/01-SetNodeProperties_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/01-SetNodeProperties_ConstraintChecks.feature index 2ac617f4d44..ab674a8bf39 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/01-SetNodeProperties_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/01-SetNodeProperties_ConstraintChecks.feature @@ -45,7 +45,7 @@ Feature: Set node properties: Constraint checks | nodeAggregateId | "nody-mc-nodeface" | | originDimensionSpacePoint | {"language":"de"} | | propertyValues | {"text":"New text"} | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to set properties in a workspace whose content stream is closed When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature index af08ffefc48..2c4aefd3639 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature @@ -77,7 +77,7 @@ Feature: Constraint checks on SetNodeReferences | sourceNodeAggregateId | "source-nodandaise" | | referenceName | "referenceProperty" | | references | [{"target":"anthony-destinode"}] | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" with code 1714839710 + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" with code 1513924741 # checks for sourceNodeAggregateId Scenario: Try to reference nodes in a non-existent node aggregate diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/01-DisableNodeAggregate_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/01-DisableNodeAggregate_ConstraintChecks.feature index 82db6e57770..c1be0796564 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/01-DisableNodeAggregate_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/01-DisableNodeAggregate_ConstraintChecks.feature @@ -39,7 +39,7 @@ Feature: Constraint checks on node aggregate disabling | workspaceName | "i-do-not-exist" | | nodeAggregateId | "sir-david-nodenborough" | | nodeVariantSelectionStrategy | "allVariants" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to disable a node aggregate in a workspace whose content stream is closed When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/04-EnableNodeAggregate_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/04-EnableNodeAggregate_ConstraintChecks.feature index 44b5734afd4..311d3d0d487 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/04-EnableNodeAggregate_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/04-EnableNodeAggregate_ConstraintChecks.feature @@ -39,7 +39,7 @@ Feature: Enable a node aggregate | workspaceName | "i-do-not-exist" | | nodeAggregateId | "sir-david-nodenborough" | | nodeVariantSelectionStrategy | "allVariants" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to enable a non-existing node aggregate When the command EnableNodeAggregate is executed with payload and exceptions are caught: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/01-RemoveNodeAggregate_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/01-RemoveNodeAggregate_ConstraintChecks.feature index bc0798286f2..301e1937186 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/01-RemoveNodeAggregate_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/01-RemoveNodeAggregate_ConstraintChecks.feature @@ -44,7 +44,7 @@ Feature: Remove NodeAggregate | nodeAggregateId | "sir-david-nodenborough" | | coveredDimensionSpacePoint | {"language":"de"} | | nodeVariantSelectionStrategy | "allVariants" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to remove a node aggregate in a workspace whose content stream is closed When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature index 47ae7031caf..721f5a667ec 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature @@ -60,7 +60,7 @@ Feature: Move node to a new parent / within the current parent before a sibling | nodeAggregateId | "sir-david-nodenborough" | | dimensionSpacePoint | {"example": "source"} | | relationDistributionStrategy | "scatter" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to move a node in a workspace whose content stream is closed: When the command CloseContentStream is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature index 542dbc3fb26..fe721eb01a0 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature @@ -42,7 +42,7 @@ Feature: Change node name | workspaceName | "i-do-not-exist" | | nodeAggregateId | "sir-david-nodenborough" | | newNodeName | "new-name" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to rename a non-existing node aggregate When the command ChangeNodeAggregateName is executed with payload and exceptions are caught: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature index 597ab0036b9..9414ce67343 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature @@ -74,7 +74,7 @@ Feature: Change node aggregate type - basic error cases | nodeAggregateId | "sir-david-nodenborough" | | newNodeTypeName | "Neos.ContentRepository.Testing:ChildOfNodeTypeA" | | strategy | "happypath" | - Then the last command should have thrown an exception of type "ContentStreamDoesNotExistYet" + Then the last command should have thrown an exception of type "WorkspaceDoesNotExist" Scenario: Try to change the type on a non-existing node aggregate When the command ChangeNodeAggregateType was published with payload and exceptions are caught: diff --git a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php index 9df36f5d2ad..c0b5270cc37 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php @@ -19,6 +19,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentStream\ContentStreamFinder; use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -55,6 +56,9 @@ public function getContentStreamFinder(): ContentStreamFinder return $this->contentRepository->getContentStreamFinder(); } + /** + * @throws WorkspaceDoesNotExist if the workspace does not exist + */ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInterface { if (isset($this->overridenContentGraphInstances[$workspaceName->value])) { From 8701a6fe4a4ab507a4f9d83a35501e91e5fe7289 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Fri, 10 May 2024 17:26:10 +0200 Subject: [PATCH 158/214] 5034 - Reduce usage of currentContentStreamId to a bare minimum --- ...eWithNode_NodeTypeConstraintChecks.feature | 4 +- .../Bootstrap/CRTestSuiteRuntimeVariables.php | 16 +++++ .../Features/Bootstrap/CRTestSuiteTrait.php | 9 ++- .../Features/ContentStreamClosing.php | 6 +- .../Features/ContentStreamForking.php | 6 +- .../Bootstrap/Features/NodeCreation.php | 3 - .../Bootstrap/Features/NodeModification.php | 3 - .../Features/Bootstrap/Features/NodeMove.php | 3 - .../Bootstrap/Features/NodeReferencing.php | 3 - .../Bootstrap/Features/NodeRemoval.php | 3 - .../Bootstrap/Features/SubtreeTagging.php | 8 +-- .../Bootstrap/ProjectedNodeAggregateTrait.php | 2 +- .../Features/Bootstrap/ProjectedNodeTrait.php | 59 ++++++++----------- .../Features/Bootstrap/BrowserTrait.php | 8 +-- 14 files changed, 55 insertions(+), 78 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/06-CreateNodeAggregateWithNode_NodeTypeConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/06-CreateNodeAggregateWithNode_NodeTypeConstraintChecks.feature index 49a1f36fe45..bbea237f9c4 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/06-CreateNodeAggregateWithNode_NodeTypeConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/06-CreateNodeAggregateWithNode_NodeTypeConstraintChecks.feature @@ -32,7 +32,6 @@ Feature: Create node aggregate with node """ And using identifier "default", I define a content repository And I am in content repository "default" - And I am in workspace "live" And I am user identified by "initiating-user-identifier" And the command CreateRootWorkspace is executed with payload: | Key | Value | @@ -41,8 +40,7 @@ Feature: Create node aggregate with node | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in content stream "cs-identifier" - And I am in dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php index 0639a9a35b5..e0143c235f7 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php @@ -103,7 +103,12 @@ public function iAmInContentStream(string $contentStreamId): void */ public function iAmInWorkspace(string $workspaceName): void { + $workspace = $this->currentContentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::fromString($workspaceName)); + if ($workspace === null) { + throw new \Exception(sprintf('Workspace "%s" does not exist, projection not yet up to date?', $workspaceName), 1548149355); + } $this->currentWorkspaceName = WorkspaceName::fromString($workspaceName); + $this->currentContentStreamId = $workspace->currentContentStreamId; } /** @@ -128,6 +133,16 @@ public function iAmInDimensionSpacePoint(string $dimensionSpacePoint): void $this->currentDimensionSpacePoint = DimensionSpacePoint::fromJsonString($dimensionSpacePoint); } + /** + * @Given /^I am in workspace "([^"]*)" and dimension space point (.*)$/ + * @throws \Exception + */ + public function iAmInWorkspaceAndDimensionSpacePoint(string $workspaceName, string $dimensionSpacePoint): void + { + $this->iAmInWorkspace($workspaceName); + $this->iAmInDimensionSpacePoint($dimensionSpacePoint); + } + /** * @Given /^I am in content stream "([^"]*)" and dimension space point (.*)$/ */ @@ -164,6 +179,7 @@ public function getCurrentSubgraph(): ContentSubgraphInterface $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); $contentGraphFinder->forgetInstances(); if (isset($this->currentContentStreamId)) { + // This must still be supported for low level tests, e.g. for content stream forking return $contentGraphFinder->getByWorkspaceNameAndContentStreamId($this->currentWorkspaceName, $this->currentContentStreamId)->getSubgraph($this->currentDimensionSpacePoint, $this->currentVisibilityConstraints); } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php index 11a35b4acac..deaa7e2941a 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteTrait.php @@ -123,7 +123,6 @@ protected function readPayloadTable(TableNode $payloadTable): array $propertyOrMethodName = \mb_substr($line['Value'], \mb_strlen('$this->')); $value = match ($propertyOrMethodName) { 'currentNodeAggregateId' => $this->getCurrentNodeAggregateId()->value, - 'contentStreamId' => $this->currentContentStreamId->value, default => method_exists($this, $propertyOrMethodName) ? (string)$this->$propertyOrMethodName() : (string)$this->$propertyOrMethodName, }; } else { @@ -287,7 +286,13 @@ public function theContentStreamHasState(string $contentStreamId, string $expect */ public function theCurrentContentStreamHasState(string $expectedState): void { - $this->theContentStreamHasState($this->currentContentStreamId->value, $expectedState); + $this->theContentStreamHasState( + $this->currentContentRepository + ->getWorkspaceFinder() + ->findOneByName($this->currentWorkspaceName) + ->currentContentStreamId->value, + $expectedState + ); } /** diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/ContentStreamClosing.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/ContentStreamClosing.php index 8e9589fc490..cf1fdeee18d 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/ContentStreamClosing.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/ContentStreamClosing.php @@ -35,11 +35,7 @@ abstract protected function readPayloadTable(TableNode $payloadTable): array; public function theCommandCloseContentStreamIsExecutedWithPayload(TableNode $payloadTable): void { $commandArguments = $this->readPayloadTable($payloadTable); - $contentStreamId = isset($commandArguments['contentStreamId']) - ? ContentStreamId::fromString($commandArguments['contentStreamId']) - : $this->currentContentStreamId; - - $command = CloseContentStream::create($contentStreamId); + $command = CloseContentStream::create(ContentStreamId::fromString($commandArguments['contentStreamId'])); $this->lastCommandOrEventResult = $this->currentContentRepository->handle($command); } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/ContentStreamForking.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/ContentStreamForking.php index c6c887a0770..ce7bae86a26 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/ContentStreamForking.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/ContentStreamForking.php @@ -36,13 +36,9 @@ abstract protected function readPayloadTable(TableNode $payloadTable): array; public function theCommandForkContentStreamIsExecutedWithPayload(TableNode $payloadTable): void { $commandArguments = $this->readPayloadTable($payloadTable); - $sourceContentStreamId = isset($commandArguments['sourceContentStreamId']) - ? ContentStreamId::fromString($commandArguments['sourceContentStreamId']) - : $this->currentContentStreamId; - $command = ForkContentStream::create( ContentStreamId::fromString($commandArguments['contentStreamId']), - $sourceContentStreamId, + ContentStreamId::fromString($commandArguments['sourceContentStreamId']), ); $this->lastCommandOrEventResult = $this->currentContentRepository->handle($command); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCreation.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCreation.php index e9338a046b8..d9fe8b59c92 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCreation.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeCreation.php @@ -94,9 +94,6 @@ public function theCommandCreateRootNodeAggregateWithNodeIsExecutedWithPayloadAn public function theEventRootNodeAggregateWithNodeWasCreatedWasPublishedToStreamWithPayload(TableNode $payloadTable) { $eventPayload = $this->readPayloadTable($payloadTable); - if (!isset($eventPayload['contentStreamId'])) { - $eventPayload['contentStreamId'] = $this->currentContentStreamId?->value; - } $contentStreamId = ContentStreamId::fromString($eventPayload['contentStreamId']); $nodeAggregateId = NodeAggregateId::fromString($eventPayload['nodeAggregateId']); $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeModification.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeModification.php index fcf7c31be82..df53480a408 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeModification.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeModification.php @@ -85,9 +85,6 @@ public function theCommandSetPropertiesIsExecutedWithPayloadAndExceptionsAreCaug public function theEventNodePropertiesWereSetWasPublishedWithPayload(TableNode $payloadTable) { $eventPayload = $this->readPayloadTable($payloadTable); - if (!isset($eventPayload['contentStreamId'])) { - $eventPayload['contentStreamId'] = $this->currentContentStreamId->value; - } if (!isset($eventPayload['originDimensionSpacePoint'])) { $eventPayload['originDimensionSpacePoint'] = json_encode($this->currentDimensionSpacePoint); } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeMove.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeMove.php index e965c3e1ca6..fcced8fa130 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeMove.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeMove.php @@ -97,9 +97,6 @@ public function theCommandMoveNodeIsExecutedWithPayloadAndExceptionsAreCaught(Ta public function theEventNodeAggregateWasMovedWasPublishedWithPayload(TableNode $payloadTable) { $eventPayload = $this->readPayloadTable($payloadTable); - if (!isset($eventPayload['contentStreamId'])) { - $eventPayload['contentStreamId'] = $this->currentContentStreamId->value; - } $contentStreamId = ContentStreamId::fromString($eventPayload['contentStreamId']); $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeReferencing.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeReferencing.php index ea391b1d359..bffc305d9b7 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeReferencing.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeReferencing.php @@ -100,9 +100,6 @@ public function theCommandSetNodeReferencesIsExecutedWithPayloadAndExceptionsAre public function theEventNodeReferencesWereSetWasPublishedWithPayload(TableNode $payloadTable) { $eventPayload = $this->readPayloadTable($payloadTable); - if (!isset($eventPayload['contentStreamId'])) { - $eventPayload['contentStreamId'] = $this->currentContentStreamId; - } $contentStreamId = ContentStreamId::fromString($eventPayload['contentStreamId']); $streamName = ContentStreamEventStreamName::fromContentStreamId( $contentStreamId diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeRemoval.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeRemoval.php index e6e28de0bd6..8a6a0139f71 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeRemoval.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/NodeRemoval.php @@ -86,9 +86,6 @@ public function theCommandRemoveNodeAggregateIsExecutedWithPayloadAndExceptionsA public function theEventNodeAggregateWasRemovedWasPublishedWithPayload(TableNode $payloadTable) { $eventPayload = $this->readPayloadTable($payloadTable); - if (!isset($eventPayload['contentStreamId'])) { - $eventPayload['contentStreamId'] = $this->currentContentStreamId->value; - } $contentStreamId = ContentStreamId::fromString($eventPayload['contentStreamId']); $streamName = ContentStreamEventStreamName::fromContentStreamId($contentStreamId); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/SubtreeTagging.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/SubtreeTagging.php index 0eae60a160f..3a4c78dba9b 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/SubtreeTagging.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/Features/SubtreeTagging.php @@ -86,9 +86,7 @@ public function theEventSubtreeWasTaggedWasPublishedWithPayload(TableNode $paylo { $eventPayload = $this->readPayloadTable($payloadTable); $streamName = ContentStreamEventStreamName::fromContentStreamId( - array_key_exists('contentStreamId', $eventPayload) - ? ContentStreamId::fromString($eventPayload['contentStreamId']) - : $this->currentContentStreamId + ContentStreamId::fromString($eventPayload['contentStreamId']) ); $this->publishEvent('SubtreeWasTagged', $streamName->getEventStreamName(), $eventPayload); @@ -104,9 +102,7 @@ public function theEventSubtreeWasUntaggedWasPublishedWithPayload(TableNode $pay { $eventPayload = $this->readPayloadTable($payloadTable); $streamName = ContentStreamEventStreamName::fromContentStreamId( - array_key_exists('contentStreamId', $eventPayload) - ? ContentStreamId::fromString($eventPayload['contentStreamId']) - : $this->currentContentStreamId + ContentStreamId::fromString($eventPayload['contentStreamId']) ); $this->publishEvent('SubtreeWasUntagged', $streamName->getEventStreamName(), $eventPayload); diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeAggregateTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeAggregateTrait.php index 38ec865c887..6b0c428e5dc 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeAggregateTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeAggregateTrait.php @@ -43,7 +43,7 @@ public function iExpectTheNodeAggregateToExist(string $serializedNodeAggregateId $nodeAggregateId = NodeAggregateId::fromString($serializedNodeAggregateId); $this->initializeCurrentNodeAggregate(function (ContentGraphInterface $contentGraph) use ($nodeAggregateId) { $currentNodeAggregate = $contentGraph->findNodeAggregateById($nodeAggregateId); - Assert::assertNotNull($currentNodeAggregate, sprintf('Node aggregate "%s" was not found in the current content stream "%s".', $nodeAggregateId->value, $this->currentContentStreamId->value)); + Assert::assertNotNull($currentNodeAggregate, sprintf('Node aggregate "%s" was not found in the current workspace "%s".', $nodeAggregateId->value, $this->currentWorkspaceName->value)); return $currentNodeAggregate; }); } diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php index 2bfe0fae891..0822a944d08 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/ProjectedNodeTrait.php @@ -81,23 +81,24 @@ public function iGetTheNodeAtPath(string $serializedNodePath): void public function iExpectANodeIdentifiedByXToExistInTheContentGraph(string $serializedNodeDiscriminator): void { $nodeDiscriminator = NodeDiscriminator::fromShorthand($serializedNodeDiscriminator); - $this->currentContentStreamId = $nodeDiscriminator->contentStreamId; - $this->initializeCurrentNodeFromContentGraph(function (ContentGraphInterface $contentGraph) use ($nodeDiscriminator) { - $currentNodeAggregate = $contentGraph->findNodeAggregateById( - $nodeDiscriminator->nodeAggregateId - ); - Assert::assertTrue( - $currentNodeAggregate?->occupiesDimensionSpacePoint($nodeDiscriminator->originDimensionSpacePoint), - 'Node with aggregate id "' . $nodeDiscriminator->nodeAggregateId->value - . '" and originating in dimension space point "' . $nodeDiscriminator->originDimensionSpacePoint->toJson() - . '" was not found in content stream "' . $nodeDiscriminator->contentStreamId->value . '"' - ); - - return $currentNodeAggregate->getNodeByOccupiedDimensionSpacePoint($nodeDiscriminator->originDimensionSpacePoint); - }); + $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); + $contentGraphFinder->forgetInstances(); + $workspaceName = $this->currentContentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId( + $nodeDiscriminator->contentStreamId + )->workspaceName; + $contentGraph = $this->currentContentRepository->getContentGraph($workspaceName); + $currentNodeAggregate = $contentGraph->findNodeAggregateById( + $nodeDiscriminator->nodeAggregateId + ); + Assert::assertTrue( + $currentNodeAggregate?->occupiesDimensionSpacePoint($nodeDiscriminator->originDimensionSpacePoint), + 'Node with aggregate id "' . $nodeDiscriminator->nodeAggregateId->value + . '" and originating in dimension space point "' . $nodeDiscriminator->originDimensionSpacePoint->toJson() + . '" was not found in content stream "' . $nodeDiscriminator->contentStreamId->value . '"' + ); + $this->currentNode = $currentNodeAggregate->getNodeByOccupiedDimensionSpacePoint($nodeDiscriminator->originDimensionSpacePoint); } - /** * @Then /^I expect node aggregate identifier "([^"]*)" to lead to node (.*)$/ */ @@ -109,7 +110,7 @@ public function iExpectNodeAggregateIdToLeadToNode( $expectedDiscriminator = NodeDiscriminator::fromShorthand($serializedNodeDiscriminator); $this->initializeCurrentNodeFromContentSubgraph(function (ContentSubgraphInterface $subgraph) use ($nodeAggregateId, $expectedDiscriminator) { $currentNode = $subgraph->findNodeById($nodeAggregateId); - Assert::assertNotNull($currentNode, 'No node could be found by node aggregate id "' . $nodeAggregateId->value . '" in content subgraph "' . $this->currentDimensionSpacePoint->toJson() . '@' . $this->currentContentStreamId->value . '"'); + Assert::assertNotNull($currentNode, 'No node could be found by node aggregate id "' . $nodeAggregateId->value . '" in content subgraph "' . $this->currentDimensionSpacePoint->toJson() . '@' . $this->currentWorkspaceName->value . '"'); $actualDiscriminator = NodeDiscriminator::fromNode($currentNode); Assert::assertTrue($expectedDiscriminator->equals($actualDiscriminator), 'Node discriminators do not match. Expected was ' . json_encode($expectedDiscriminator) . ' , given was ' . json_encode($actualDiscriminator)); return $currentNode; @@ -126,7 +127,7 @@ public function iExpectNodeAggregateIdToLeadToNoNode(string $serializedNodeAggre if (!is_null($nodeByAggregateId)) { Assert::fail( 'A node was found by node aggregate id "' . $nodeAggregateId->value - . '" in content subgraph {' . $this->currentDimensionSpacePoint->toJson() . ',' . $this->currentContentStreamId->value . '}' + . '" in content subgraph {' . $this->currentDimensionSpacePoint->toJson() . ',' . $this->currentWorkspaceName->value . '}' ); } } @@ -146,7 +147,7 @@ public function iExpectPathToLeadToNode(string $serializedNodePath, string $seri $expectedDiscriminator = NodeDiscriminator::fromShorthand($serializedNodeDiscriminator); $this->initializeCurrentNodeFromContentSubgraph(function (ContentSubgraphInterface $subgraph) use ($nodePath, $expectedDiscriminator) { $currentNode = $subgraph->findNodeByPath($nodePath, $this->getRootNodeAggregateId()); - Assert::assertNotNull($currentNode, 'No node could be found by node path "' . $nodePath->serializeToString() . '" in content subgraph "' . $this->currentDimensionSpacePoint->toJson() . '@' . $this->currentContentStreamId->value . '"'); + Assert::assertNotNull($currentNode, 'No node could be found by node path "' . $nodePath->serializeToString() . '" in content subgraph "' . $this->currentDimensionSpacePoint->toJson() . '@' . $this->currentWorkspaceName->value . '"'); $actualDiscriminator = NodeDiscriminator::fromNode($currentNode); Assert::assertTrue($expectedDiscriminator->equals($actualDiscriminator), 'Node discriminators do not match. Expected was ' . json_encode($expectedDiscriminator) . ' , given was ' . json_encode($actualDiscriminator)); return $currentNode; @@ -168,7 +169,7 @@ public function iExpectPathToLeadToNoNode(string $serializedNodePath): void Assert::assertNull( $nodeByPath, 'A node was found by node path "' . $nodePath->serializeToString() - . '" in content subgraph "' . $this->currentDimensionSpacePoint->toJson() . '@' . $this->currentContentStreamId->value . '"' + . '" in content subgraph "' . $this->currentDimensionSpacePoint->toJson() . '@' . $this->currentWorkspaceName->value . '"' ); } @@ -206,7 +207,7 @@ public function iExpectTheNodeWithAggregateIdentifierToBeExplicitlyTagged(string $expectedTag = SubtreeTag::fromString($serializedTag); $this->initializeCurrentNodeFromContentSubgraph(function (ContentSubgraphInterface $subgraph) use ($nodeAggregateId, $expectedTag) { $currentNode = $subgraph->findNodeById($nodeAggregateId); - Assert::assertNotNull($currentNode, 'No node could be found by node aggregate id "' . $nodeAggregateId->value . '" in content subgraph "' . $this->currentDimensionSpacePoint->toJson() . '@' . $this->currentContentStreamId->value . '"'); + Assert::assertNotNull($currentNode, 'No node could be found by node aggregate id "' . $nodeAggregateId->value . '" in content subgraph "' . $this->currentDimensionSpacePoint->toJson() . '@' . $this->currentWorkspaceName->value . '"'); Assert::assertTrue($currentNode->tags->withoutInherited()->contain($expectedTag)); return $currentNode; }); @@ -221,7 +222,7 @@ public function iExpectTheNodeWithAggregateIdentifierToInheritTheTag(string $ser $expectedTag = SubtreeTag::fromString($serializedTag); $this->initializeCurrentNodeFromContentSubgraph(function (ContentSubgraphInterface $subgraph) use ($nodeAggregateId, $expectedTag) { $currentNode = $subgraph->findNodeById($nodeAggregateId); - Assert::assertNotNull($currentNode, 'No node could be found by node aggregate id "' . $nodeAggregateId->value . '" in content subgraph "' . $this->currentDimensionSpacePoint->toJson() . '@' . $this->currentContentStreamId->value . '"'); + Assert::assertNotNull($currentNode, 'No node could be found by node aggregate id "' . $nodeAggregateId->value . '" in content subgraph "' . $this->currentDimensionSpacePoint->toJson() . '@' . $this->currentWorkspaceName->value . '"'); Assert::assertTrue($currentNode->tags->onlyInherited()->contain($expectedTag)); return $currentNode; }); @@ -236,8 +237,8 @@ public function iExpectTheNodeWithAggregateIdentifierToNotContainTheTag(string $ $expectedTag = SubtreeTag::fromString($serializedTag); $this->initializeCurrentNodeFromContentSubgraph(function (ContentSubgraphInterface $subgraph) use ($nodeAggregateId, $expectedTag) { $currentNode = $subgraph->findNodeById($nodeAggregateId); - Assert::assertNotNull($currentNode, 'No node could be found by node aggregate id "' . $nodeAggregateId->value . '" in content subgraph "' . $this->currentDimensionSpacePoint->toJson() . '@' . $this->currentContentStreamId->value . '"'); - Assert::assertFalse($currentNode->tags->contain($expectedTag), sprintf('Node with id "%s" in content subgraph "%s@%s", was not expected to contain the subtree tag "%s" but it does', $nodeAggregateId->value, $this->currentDimensionSpacePoint->toJson(), $this->currentContentStreamId->value, $expectedTag->value)); + Assert::assertNotNull($currentNode, 'No node could be found by node aggregate id "' . $nodeAggregateId->value . '" in content subgraph "' . $this->currentDimensionSpacePoint->toJson() . '@' . $this->currentWorkspaceName->value . '"'); + Assert::assertFalse($currentNode->tags->contain($expectedTag), sprintf('Node with id "%s" in content subgraph "%s@%s", was not expected to contain the subtree tag "%s" but it does', $nodeAggregateId->value, $this->currentDimensionSpacePoint->toJson(), $this->currentWorkspaceName->value, $expectedTag->value)); return $currentNode; }); } @@ -274,18 +275,6 @@ public function iExpectThisNodeToExactlyInheritTheTags(string $expectedTagList): }); } - protected function initializeCurrentNodeFromContentGraph(callable $query): void - { - $contentGraphFinder = $this->currentContentRepository->projectionState(ContentGraphFinder::class); - $contentGraphFinder->forgetInstances(); - if (isset($this->currentContentStreamId)) { - $contentGraph = $contentGraphFinder->getByWorkspaceNameAndContentStreamId($this->currentWorkspaceName, $this->currentContentStreamId); - } else { - $contentGraph = $this->currentContentRepository->getContentGraph($this->currentWorkspaceName); - } - $this->currentNode = $query($contentGraph); - } - protected function initializeCurrentNodeFromContentSubgraph(callable $query): void { $this->currentNode = $query($this->getCurrentSubgraph()); diff --git a/Neos.Neos/Tests/Behavior/Features/Bootstrap/BrowserTrait.php b/Neos.Neos/Tests/Behavior/Features/Bootstrap/BrowserTrait.php index 8783cb9d480..a5454d0352e 100644 --- a/Neos.Neos/Tests/Behavior/Features/Bootstrap/BrowserTrait.php +++ b/Neos.Neos/Tests/Behavior/Features/Bootstrap/BrowserTrait.php @@ -122,9 +122,7 @@ public function iGetTheNodeAddressForNodeAggregate(string $rawNodeAggregateId, $ $this->currentContentStreamId, $this->currentDimensionSpacePoint, $nodeAggregateId, - $this->currentContentRepository->getWorkspaceFinder() - ->findOneByCurrentContentStreamId($this->currentContentStreamId) - ->workspaceName + $this->currentWorkspaceName, ); } @@ -147,9 +145,7 @@ public function iGetTheNodeAddressForTheNodeAtPath(string $serializedNodePath, $ $this->currentContentStreamId, $this->currentDimensionSpacePoint, $node->nodeAggregateId, - $this->currentContentRepository->getWorkspaceFinder() - ->findOneByCurrentContentStreamId($this->currentContentStreamId) - ->workspaceName + $this->currentWorkspaceName, ); } From a772403ce8b7b3311590a511f3b39e39b2646e7b Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Fri, 10 May 2024 18:53:35 +0200 Subject: [PATCH 159/214] 5034 - Refactor test suite to work on workspace where applicable --- .../HierarchyIntegrityIsProvided.feature | 2 +- ...odesHaveAtMostOneParentPerSubgraph.feature | 2 +- .../ReferenceIntegrityIsProvided.feature | 2 +- .../SiblingsAreDistinctlySorted.feature | 2 +- .../SubtreeTagsAreInherited.feature | 2 +- .../TetheredNodesAreNamed.feature | 2 +- .../Projection/SiblingPositions.feature | 4 +- ...AggregateWithNode_ConstraintChecks.feature | 2 +- ...ggregateWithNode_WithoutDimensions.feature | 2 +- ...TetheredChildren_WithoutDimensions.feature | 4 +- ...AndTetheredChildren_WithDimensions.feature | 10 +- ...deAggregateWithNode_WithDimensions.feature | 2 +- ...AggregateWithNode_ConstraintChecks.feature | 2 +- ...de_ConstraintChecks_WithDimensions.feature | 2 +- ...ggregateWithNode_WithoutDimensions.feature | 14 +- ...deAggregateWithNode_WithDimensions.feature | 2 +- ...ComplexDefaultAndInitialProperties.feature | 2 +- ...CreateNodeVariant_ConstraintChecks.feature | 2 +- ...02-CreateNodeSpecializationVariant.feature | 16 +- ...03-CreateNodeGeneralizationVariant.feature | 32 +- .../04-CreateNodePeerVariant.feature | 28 +- ...SetNodeProperties_ConstraintChecks.feature | 2 +- .../02-SetNodeProperties.feature | 2 +- ...3-SetNodeProperties_PropertyScopes.feature | 2 +- ...SetNodeReferences_ConstraintChecks.feature | 2 +- ...etNodeReferences_WithoutDimensions.feature | 2 +- ...3-SetNodeReferences_WithDimensions.feature | 6 +- ...4-SetNodeReferences_PropertyScopes.feature | 8 +- ...odeVariation_After_NodeReferencing.feature | 30 +- ...ableNodeAggregate_ConstraintChecks.feature | 2 +- ...bleNodeAggregate_WithoutDimensions.feature | 6 +- ...isableNodeAggregate_WithDimensions.feature | 6 +- ...ableNodeAggregate_ConstraintChecks.feature | 2 +- ...bleNodeAggregate_WithoutDimensions.feature | 14 +- ...EnableNodeAggregate_WithDimensions.feature | 8 +- ...DisabledAncestor_WithoutDimensions.feature | 2 +- ...ithDisabledAncestor_WithDimensions.feature | 2 +- ...09-CreateNodeVariantOfDisabledNode.feature | 2 +- ...moveNodeAggregate_ConstraintChecks.feature | 2 +- ...oveNodeAggregate_WithoutDimensions.feature | 2 +- ...RemoveNodeAggregate_WithDimensions.feature | 2 +- .../04-VariantRecreation.feature | 4 +- .../05-CreateNodeAfterDeletion.feature | 4 +- ...SpecializationVariantAfterDeletion.feature | 4 +- .../01-MoveNodes_ConstraintChecks.feature | 2 +- ...deAggregate_NoNewParent_Dimensions.feature | 250 +++++++------- ...NodeAggregate_NewParent_Dimensions.feature | 252 +++++++------- ...oveNodeAggregate_ScatteredChildren.feature | 34 +- .../05-MoveNodeAggregate_SubtreeTags.feature | 322 +++++++++--------- .../06-AdditionalConstraintChecks.feature | 2 +- ...MoveNodeAggregateWithoutDimensions.feature | 10 +- ...ForkContentStream_ConstraintChecks.feature | 2 +- ...WithDisabledNodesWithoutDimensions.feature | 2 +- ...ForkContentStreamWithoutDimensions.feature | 2 +- .../NodeReferencesOnForkContentStream.feature | 2 +- .../AddDimensionShineThrough.feature | 8 +- .../AddNewProperty_NoDimensions.feature | 4 +- .../ChangePropertyValue_Dimensions.feature | 8 +- .../ChangePropertyValue_NoDimensions.feature | 4 +- .../Filter_NodeName_NoDimensions.feature | 4 +- ...lter_PropertyNotEmpty_NoDimensions.feature | 4 +- .../Filter_PropertyValue_NoDimensions.feature | 4 +- .../Migration/MoveDimensionSpacePoint.feature | 6 +- .../NodeTypeAdjustment_Dimensions.feature | 6 +- .../NodeTypeAdjustment_NoDimensions.feature | 4 +- .../Migration/RemoveNodes_Dimensions.feature | 38 +-- .../RemoveProperty_NoDimensions.feature | 4 +- .../RenameNodeAggregate_Dimensions.feature | 6 +- .../RenameProperty_NoDimensions.feature | 4 +- .../StripTagsOnProperty_NoDimensions.feature | 4 +- .../NodeCopying/CopyNode_NoDimensions.feature | 4 +- .../NodePropertyConversion.feature | 6 +- .../RemoveNodeAggregateAfterDisabling.feature | 6 +- .../RemoveNodeAggregateWithDimensions.feature | 16 +- ...NodeAggregateName_ConstraintChecks.feature | 2 +- .../ChangeNodeAggregateName.feature | 4 +- ...eNodeAggregateType_BasicErrorCases.feature | 2 +- ...geNodeAggregateType_DeleteStrategy.feature | 20 +- ...odeAggregateType_HappyPathStrategy.feature | 6 +- .../NodeTraversal/AncestorNodes.feature | 2 +- .../Features/NodeTraversal/ChildNodes.feature | 2 +- .../NodeTraversal/ClosestNode.feature | 2 +- .../Features/NodeTraversal/CountNodes.feature | 2 +- .../NodeTraversal/DescendantNodes.feature | 2 +- .../NodeTraversal/FindNodeById.feature | 2 +- .../NodeTraversal/FindNodeByPath.feature | 2 +- .../FindNodeByPathAsNodeName.feature | 2 +- .../NodeTraversal/FindParentNode.feature | 2 +- .../NodeTraversal/FindRootNodeByType.feature | 2 +- .../NodeTraversal/FindSubtree.feature | 2 +- .../Features/NodeTraversal/References.feature | 2 +- .../NodeTraversal/RetrieveNodePath.feature | 2 +- .../NodeTraversal/SiblingNodes.feature | 2 +- .../Features/NodeTraversal/Timestamps.feature | 52 +-- .../AllNodesCoverTheirOrigin.feature | 2 +- ...ateIdentifiersAreUniquePerSubgraph.feature | 2 +- ...istentlyClassifiedPerContentStream.feature | 2 +- ...eConsistentlyTypedPerContentStream.feature | 2 +- .../ReferenceIntegrityIsProvided.feature | 2 +- ...AggregateDimensions_WithDimensions.feature | 2 +- .../DimensionMismatch.feature | 2 +- .../DisallowedChildNode.feature | 8 +- ...sallowedChildNodesAndTetheredNodes.feature | 2 +- .../StructureAdjustment/Properties.feature | 8 +- .../StructureAdjustment/TetheredNodes.feature | 6 +- .../TetheredNodesReordering.feature | 4 +- .../UnknownNodeType.feature | 4 +- .../TagSubtree_WithDimensions.feature | 4 +- .../TagSubtree_WithoutDimensions.feature | 6 +- .../01-ConstraintChecks.feature | 4 +- .../02-BasicFeatures.feature | 10 +- .../02-RebasingWithAutoCreatedNodes.feature | 4 +- .../03-RebasingWithConflictingChanges.feature | 2 +- .../02-PublishWorkspace.feature | 14 +- .../01-ConstraintChecks.feature | 4 +- .../02-BasicFeatures.feature | 4 +- .../03-MoreBasicFeatures.feature | 14 +- .../04-AllFeaturePublication.feature | 30 +- ...PublishMovedNodesWithoutDimensions.feature | 10 +- .../02-DiscardWorkspace.feature | 8 +- ...NodeOperationsOnMultipleWorkspaces.feature | 10 +- .../Workspaces/PruneContentStreams.feature | 6 +- ...ingleNodeOperationsOnLiveWorkspace.feature | 4 +- .../Bootstrap/CRTestSuiteRuntimeVariables.php | 24 -- .../Features/ContentCache/Assets.feature | 8 +- .../Features/ContentCache/ConvertUris.feature | 8 +- .../Features/ContentCache/Nodes.feature | 2 +- .../NodesInOtherWorkspace.feature | 26 +- .../Features/FrontendRouting/Basic.feature | 4 +- .../FrontendRouting/Dimensions.feature | 2 +- .../FrontendRouting/DisableNodes.feature | 2 +- .../Lowlevel_ProjectionTests.feature | 6 +- .../FrontendRouting/MultiSiteLinking.feature | 2 +- .../NodeCreationEdgeCases.feature | 2 +- .../NodeVariationEdgeCases.feature | 6 +- .../FrontendRouting/RouteCache.feature | 4 +- .../FrontendRouting/Shortcuts.feature | 8 +- .../TetheredSiteChildDocuments.feature | 2 +- .../FrontendRouting/UnknownNodeTypes.feature | 2 +- .../Features/Fusion/ContentCase.feature | 2 +- .../Features/Fusion/ContentCollection.feature | 2 +- .../Features/Fusion/ConvertUris.feature | 2 +- .../Behavior/Features/Fusion/Menu.feature | 2 +- .../Behavior/Features/HandleExceeded.feature | 2 +- 144 files changed, 830 insertions(+), 854 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/HierarchyIntegrityIsProvided.feature b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/HierarchyIntegrityIsProvided.feature index bb7eeccc08b..29e8723a50c 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/HierarchyIntegrityIsProvided.feature +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/HierarchyIntegrityIsProvided.feature @@ -20,7 +20,7 @@ Feature: Run integrity violation detection regarding hierarchy relations and nod | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/NodesHaveAtMostOneParentPerSubgraph.feature b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/NodesHaveAtMostOneParentPerSubgraph.feature index 0cb73f49e4b..72ced95d05a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/NodesHaveAtMostOneParentPerSubgraph.feature +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/NodesHaveAtMostOneParentPerSubgraph.feature @@ -20,7 +20,7 @@ Feature: Run integrity violation detection regarding parent relations | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/ReferenceIntegrityIsProvided.feature b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/ReferenceIntegrityIsProvided.feature index 3267be5d085..3e02af08377 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/ReferenceIntegrityIsProvided.feature +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/ReferenceIntegrityIsProvided.feature @@ -23,7 +23,7 @@ Feature: Run integrity violation detection regarding reference relations | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/SiblingsAreDistinctlySorted.feature b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/SiblingsAreDistinctlySorted.feature index 75f7032d08c..433d1a30c88 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/SiblingsAreDistinctlySorted.feature +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/SiblingsAreDistinctlySorted.feature @@ -20,7 +20,7 @@ Feature: Run integrity violation detection regarding sibling sorting | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/SubtreeTagsAreInherited.feature b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/SubtreeTagsAreInherited.feature index 1c424425d1f..c9a2769dfb2 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/SubtreeTagsAreInherited.feature +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/SubtreeTagsAreInherited.feature @@ -20,7 +20,7 @@ Feature: Run integrity violation detection regarding subtree tag inheritance | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/TetheredNodesAreNamed.feature b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/TetheredNodesAreNamed.feature index 3068aaa7edf..65703ac4a8f 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/TetheredNodesAreNamed.feature +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/ProjectionIntegrityViolationDetection/TetheredNodesAreNamed.feature @@ -20,7 +20,7 @@ Feature: Run projection integrity violation detection regarding naming of tether | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/SiblingPositions.feature b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/SiblingPositions.feature index 704e7c58933..6ace4b14ce3 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/SiblingPositions.feature +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Projection/SiblingPositions.feature @@ -22,7 +22,7 @@ Feature: Sibling positions are properly resolved | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I am in workspace "live" and dimension space point {"example": "general"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -43,7 +43,7 @@ Feature: Sibling positions are properly resolved Scenario: Trigger position update in DBAL graph - Given I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + Given I am in workspace "live" and dimension space point {"example": "general"} # distance i to x: 128 # distance ii to x: 64 When the command MoveNodeAggregate is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/01-CreateRootNodeAggregateWithNode_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/01-CreateRootNodeAggregateWithNode_ConstraintChecks.feature index d7c4a1716df..399af60a076 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/01-CreateRootNodeAggregateWithNode_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/01-CreateRootNodeAggregateWithNode_ConstraintChecks.feature @@ -27,7 +27,7 @@ Feature: Create a root node aggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/02-CreateRootNodeAggregateWithNode_WithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/02-CreateRootNodeAggregateWithNode_WithoutDimensions.feature index 9f86bc72a56..5573499f8a6 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/02-CreateRootNodeAggregateWithNode_WithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/02-CreateRootNodeAggregateWithNode_WithoutDimensions.feature @@ -25,7 +25,7 @@ Feature: Create a root node aggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Scenario: Create the initial root node aggregate using valid payload without dimensions When the command CreateRootNodeAggregateWithNode is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/03-CreateRootNodeAggregateWithNodeAndTetheredChildren_WithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/03-CreateRootNodeAggregateWithNodeAndTetheredChildren_WithoutDimensions.feature index 163142ec3dd..28f465e078a 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/03-CreateRootNodeAggregateWithNodeAndTetheredChildren_WithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/03-CreateRootNodeAggregateWithNodeAndTetheredChildren_WithoutDimensions.feature @@ -38,7 +38,7 @@ Feature: Create a root node aggregate with tethered children | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And I am user identified by "initiating-user-identifier" Scenario: Create root node with tethered children @@ -133,7 +133,7 @@ Feature: Create a root node aggregate with tethered children | Key | Value | | text | "my sub sub default" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no parent node And I expect this node to have the following child nodes: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/04-CreateRootNodeAggregateWithNodeAndTetheredChildren_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/04-CreateRootNodeAggregateWithNodeAndTetheredChildren_WithDimensions.feature index 2f4455862d8..8bc71cbd9e9 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/04-CreateRootNodeAggregateWithNodeAndTetheredChildren_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/04-CreateRootNodeAggregateWithNodeAndTetheredChildren_WithDimensions.feature @@ -40,7 +40,7 @@ Feature: Create a root node aggregate with tethered children | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And I am user identified by "initiating-user-identifier" Scenario: Create root node with tethered children @@ -173,7 +173,7 @@ Feature: Create a root node aggregate with tethered children | Key | Value | | text | "my sub sub default" | - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} And I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no parent node And I expect this node to have the following child nodes: @@ -202,7 +202,7 @@ Feature: Create a root node aggregate with tethered children And I expect this node to have no references And I expect this node to not be referenced - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} + When I am in workspace "live" and dimension space point {"language": "gsw"} And I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no parent node And I expect this node to have the following child nodes: @@ -232,7 +232,7 @@ Feature: Create a root node aggregate with tethered children And I expect this node to not be referenced - When I am in the active content stream of workspace "live" and dimension space point {"language": "en"} + When I am in workspace "live" and dimension space point {"language": "en"} And I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no parent node And I expect this node to have the following child nodes: @@ -262,7 +262,7 @@ Feature: Create a root node aggregate with tethered children And I expect this node to not be referenced - When I am in the active content stream of workspace "live" and dimension space point {"language": "en_US"} + When I am in workspace "live" and dimension space point {"language": "en_US"} And I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no parent node And I expect this node to have the following child nodes: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/05-CreateRootNodeAggregateWithNode_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/05-CreateRootNodeAggregateWithNode_WithDimensions.feature index af98e698f3f..e8370d00f6f 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/05-CreateRootNodeAggregateWithNode_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/01-RootNodeCreation/05-CreateRootNodeAggregateWithNode_WithDimensions.feature @@ -26,7 +26,7 @@ Feature: Create a root node aggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Scenario: Create the initial root node aggregate using valid payload with dimensions When the command CreateRootNodeAggregateWithNode is executed with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature index da3862003de..d62848644d9 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/01-CreateNodeAggregateWithNode_ConstraintChecks.feature @@ -38,7 +38,7 @@ Feature: Create node aggregate with node | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature index 3fd276365f5..42b2c287de4 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/02-CreateNodeAggregateWithNode_ConstraintChecks_WithDimensions.feature @@ -40,7 +40,7 @@ Feature: Create node aggregate with node | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And I am in dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/03-CreateNodeAggregateWithNode_WithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/03-CreateNodeAggregateWithNode_WithoutDimensions.feature index 4bbca7c61d7..040ab0e5576 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/03-CreateNodeAggregateWithNode_WithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/03-CreateNodeAggregateWithNode_WithoutDimensions.feature @@ -30,7 +30,7 @@ Feature: Create node aggregate with node | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + 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: @@ -149,7 +149,7 @@ Feature: Create node aggregate with node | Key | Value | | defaultText | "my default" | - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect the subgraph projection to consist of exactly 4 nodes And I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no parent node @@ -201,7 +201,7 @@ Feature: Create node aggregate with node | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + 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: @@ -241,7 +241,7 @@ Feature: Create node aggregate with node | nodeAggregateClassification | "regular" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And I expect node aggregate identifier "sir-nodeward-nodington-iii" and node path "esquire" to lead to node cs-identifier;sir-nodeward-nodington-iii;{} And I expect this node to be a child of node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no child nodes @@ -285,7 +285,7 @@ Feature: Create node aggregate with node | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + 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: @@ -407,7 +407,7 @@ Feature: Create node aggregate with node | Key | Value | | text | "my sub sub default" | - When I am in the active content stream of workspace "live" and dimension space point [] + When I am in workspace "live" and dimension space point [] And I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no parent node And I expect this node to have the following child nodes: @@ -469,7 +469,7 @@ Feature: Create node aggregate with node | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + 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: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/04-CreateNodeAggregateWithNode_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/04-CreateNodeAggregateWithNode_WithDimensions.feature index 96d4734ebce..ff82b174f1c 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/04-CreateNodeAggregateWithNode_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/04-CreateNodeAggregateWithNode_WithDimensions.feature @@ -30,7 +30,7 @@ Feature: Create node aggregate with node | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/05-CreateNodeAggregateWithNode_ComplexDefaultAndInitialProperties.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/05-CreateNodeAggregateWithNode_ComplexDefaultAndInitialProperties.feature index 22f043d0590..5e55c88ec77 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/05-CreateNodeAggregateWithNode_ComplexDefaultAndInitialProperties.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/02-NodeCreation/05-CreateNodeAggregateWithNode_ComplexDefaultAndInitialProperties.feature @@ -55,7 +55,7 @@ Feature: Create a node aggregate with complex default values | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And I am in dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/01-CreateNodeVariant_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/01-CreateNodeVariant_ConstraintChecks.feature index 2105bfd50e9..4db55956655 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/01-CreateNodeVariant_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/01-CreateNodeVariant_ConstraintChecks.feature @@ -26,7 +26,7 @@ Feature: Create node variant | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"market":"DE", "language":"gsw"} + And I am in workspace "live" and dimension space point {"market":"DE", "language":"gsw"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/02-CreateNodeSpecializationVariant.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/02-CreateNodeSpecializationVariant.feature index 38734f8514a..66200bdea63 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/02-CreateNodeSpecializationVariant.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/02-CreateNodeSpecializationVariant.feature @@ -30,7 +30,7 @@ Feature: Create node specialization | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And I am in workspace "live" and dimension space point {"example":"source"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -49,7 +49,7 @@ Feature: Create node specialization | invariable-mc-nodeface | invariable-document | nody-mc-nodeface | | Neos.ContentRepository.Testing:LeafDocument | {} | Scenario: check the tree state before the specialization - When I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + When I am in workspace "live" and dimension space point {"example":"source"} And the subtree for node aggregate "lady-eleonode-rootford" with node types "" and 3 levels deep should be: | Level | nodeAggregateId | | 0 | lady-eleonode-rootford | @@ -106,7 +106,7 @@ Feature: Create node specialization And I expect a node identified by cs-identifier;youngest-mc-nodeface;{"example":"source"} to exist in the content graph And I expect a node identified by cs-identifier;invariable-mc-nodeface;{"example":"source"} to exist in the content graph - When I am in the active content stream of workspace "live" + 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 [{"example":"source"},{"example":"spec"},{"example":"leafSpec"}] @@ -143,7 +143,7 @@ Feature: Create node specialization And I expect this node aggregate to occupy dimension space points [{"example":"source"}] And I expect this node aggregate to cover dimension space points [{"example":"source"},{"example":"spec"},{"example":"leafSpec"}] - When I am in the active content stream of workspace "live" and dimension space point {"example":"spec"} + When I am in workspace "live" and dimension space point {"example":"spec"} Then I expect the subgraph projection to consist of exactly 9 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And the subtree for node aggregate "lady-eleonode-rootford" with node types "" and 3 levels deep should be: @@ -218,7 +218,7 @@ Feature: Create node specialization | cs-identifier;eldest-mc-nodeface;{"example":"source"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example":"leafSpec"} + When I am in workspace "live" and dimension space point {"example":"leafSpec"} Then I expect the subgraph projection to consist of exactly 9 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -335,7 +335,7 @@ Feature: Create node specialization And I expect a node identified by cs-identifier;youngest-mc-nodeface;{"example":"source"} to exist in the content graph And I expect a node identified by cs-identifier;invariable-mc-nodeface;{"example":"source"} to exist in the content graph - When I am in the active content stream of workspace "live" + 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 [{"example":"source"},{"example":"spec"},{"example":"leafSpec"}] @@ -372,7 +372,7 @@ Feature: Create node specialization And I expect this node aggregate to occupy dimension space points [{"example":"source"}] And I expect this node aggregate to cover dimension space points [{"example":"source"},{"example":"spec"},{"example":"leafSpec"}] - When I am in the active content stream of workspace "live" and dimension space point {"example":"spec"} + When I am in workspace "live" and dimension space point {"example":"spec"} Then I expect the subgraph projection to consist of exactly 9 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -436,7 +436,7 @@ Feature: Create node specialization | cs-identifier;eldest-mc-nodeface;{"example":"source"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example":"leafSpec"} + When I am in workspace "live" and dimension space point {"example":"leafSpec"} Then I expect the subgraph projection to consist of exactly 9 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/03-CreateNodeGeneralizationVariant.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/03-CreateNodeGeneralizationVariant.feature index 55bddb932f0..7fcf542fdf0 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/03-CreateNodeGeneralizationVariant.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/03-CreateNodeGeneralizationVariant.feature @@ -30,7 +30,7 @@ Feature: Create node generalization | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And I am in workspace "live" and dimension space point {"example":"source"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -123,7 +123,7 @@ Feature: Create node generalization And I expect a node identified by cs-identifier;youngest-mc-nodeface;{"example":"general"} to exist in the content graph And I expect a node identified by cs-identifier;invariable-mc-nodeface;{"example":"source"} to exist in the content graph - When I am in the active content stream of workspace "live" + 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 [{"example":"rootGeneral"},{"example":"general"},{"example":"source"},{"example":"specB"}] @@ -160,7 +160,7 @@ Feature: Create node generalization And I expect this node aggregate to occupy dimension space points [{"example":"source"},{"example":"general"}] And I expect this node aggregate to cover dimension space points [{"example":"general"},{"example":"source"},{"example":"specB"}] - When I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + When I am in workspace "live" and dimension space point {"example":"source"} Then I expect the subgraph projection to consist of exactly 9 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -221,7 +221,7 @@ Feature: Create node generalization | cs-identifier;eldest-mc-nodeface;{"example":"general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example":"general"} + When I am in workspace "live" and dimension space point {"example":"general"} Then I expect the subgraph projection to consist of exactly 8 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -286,7 +286,7 @@ Feature: Create node generalization And I expect this node to have no succeeding siblings And I expect node aggregate identifier "invariable-mc-nodeface" and node path "document/invariable-document" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"example":"rootGeneral"} + When I am in workspace "live" and dimension space point {"example":"rootGeneral"} Then I expect the subgraph projection to consist of exactly 1 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no child nodes @@ -299,7 +299,7 @@ Feature: Create node generalization And I expect node aggregate identifier "youngest-mc-nodeface" and node path "youngest-document" to lead to no node And I expect node aggregate identifier "invariable-mc-nodeface" and node path "document/invariable-document" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"example":"specB"} + When I am in workspace "live" and dimension space point {"example":"specB"} Then I expect the subgraph projection to consist of exactly 8 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -439,7 +439,7 @@ Feature: Create node generalization And I expect a node identified by cs-identifier;youngest-mc-nodeface;{"example":"source"} to exist in the content graph And I expect a node identified by cs-identifier;invariable-mc-nodeface;{"example":"source"} to exist in the content graph - When I am in the active content stream of workspace "live" + 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 [{"example":"rootGeneral"},{"example":"general"},{"example":"source"},{"example":"specB"}] @@ -472,7 +472,7 @@ Feature: Create node generalization And I expect this node aggregate to occupy dimension space points [{"example":"source"}] And I expect this node aggregate to cover dimension space points [{"example":"source"}] - When I am in the active content stream of workspace "live" and dimension space point {"example":"general"} + When I am in workspace "live" and dimension space point {"example":"general"} Then I expect the subgraph projection to consist of exactly 6 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -511,7 +511,7 @@ Feature: Create node generalization And I expect node aggregate identifier "youngest-mc-nodeface" and node path "youngest-document" to lead to no node And I expect node aggregate identifier "invariable-mc-nodeface" and node path "document/invariable-document" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"example": "specB"} + When I am in workspace "live" and dimension space point {"example": "specB"} Then I expect the subgraph projection to consist of exactly 6 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -625,7 +625,7 @@ Feature: Create node generalization And I expect a node identified by cs-identifier;youngest-mc-nodeface;{"example":"source"} to exist in the content graph And I expect a node identified by cs-identifier;invariable-mc-nodeface;{"example":"source"} to exist in the content graph - When I am in the active content stream of workspace "live" + 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 [{"example":"rootGeneral"},{"example":"general"},{"example":"source"},{"example":"specB"}] @@ -658,7 +658,7 @@ Feature: Create node generalization And I expect this node aggregate to occupy dimension space points [{"example":"source"}] And I expect this node aggregate to cover dimension space points [{"example":"source"}] - When I am in the active content stream of workspace "live" and dimension space point {"example":"general"} + When I am in workspace "live" and dimension space point {"example":"general"} Then I expect the subgraph projection to consist of exactly 6 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -697,7 +697,7 @@ Feature: Create node generalization And I expect node aggregate identifier "youngest-mc-nodeface" and node path "youngest-document" to lead to no node And I expect node aggregate identifier "invariable-mc-nodeface" and node path "document/invariable-document" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"example": "specB"} + When I am in workspace "live" and dimension space point {"example": "specB"} Then I expect the subgraph projection to consist of exactly 6 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -811,7 +811,7 @@ Feature: Create node generalization And I expect a node identified by cs-identifier;youngest-mc-nodeface;{"example":"source"} to exist in the content graph And I expect a node identified by cs-identifier;invariable-mc-nodeface;{"example":"source"} to exist in the content graph - When I am in the active content stream of workspace "live" + 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 [{"example":"rootGeneral"},{"example":"general"},{"example":"source"},{"example":"specB"}] @@ -844,7 +844,7 @@ Feature: Create node generalization And I expect this node aggregate to occupy dimension space points [{"example":"source"}] And I expect this node aggregate to cover dimension space points [{"example":"source"}] - When I am in the active content stream of workspace "live" and dimension space point {"example":"rootGeneral"} + When I am in workspace "live" and dimension space point {"example":"rootGeneral"} Then I expect the subgraph projection to consist of exactly 4 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -867,7 +867,7 @@ Feature: Create node generalization And I expect node aggregate identifier "youngest-mc-nodeface" and node path "youngest-document" to lead to no node And I expect node aggregate identifier "invariable-mc-nodeface" and node path "document/invariable-document" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect the subgraph projection to consist of exactly 6 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -906,7 +906,7 @@ Feature: Create node generalization And I expect node aggregate identifier "youngest-mc-nodeface" and node path "youngest-document" to lead to no node And I expect node aggregate identifier "invariable-mc-nodeface" and node path "document/invariable-document" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"example": "specB"} + When I am in workspace "live" and dimension space point {"example": "specB"} Then I expect the subgraph projection to consist of exactly 6 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/04-CreateNodePeerVariant.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/04-CreateNodePeerVariant.feature index 2a3ef7c7f8f..5b60071e11a 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/04-CreateNodePeerVariant.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/03-NodeVariation/04-CreateNodePeerVariant.feature @@ -30,7 +30,7 @@ Feature: Create node peer variant | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And I am in workspace "live" and dimension space point {"example":"source"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -132,7 +132,7 @@ Feature: Create node peer variant And I expect a node identified by cs-identifier;youngest-mc-nodeface;{"example":"peer"} to exist in the content graph And I expect a node identified by cs-identifier;invariable-mc-nodeface;{"example":"source"} to exist in the content graph - When I am in the active content stream of workspace "live" + 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 [{"example":"source"},{"example":"peer"},{"example":"peerSpec"}] @@ -169,7 +169,7 @@ Feature: Create node peer variant And I expect this node aggregate to occupy dimension space points [{"example":"source"},{"example":"peer"}] And I expect this node aggregate to cover dimension space points [{"example":"source"},{"example":"peer"},{"example":"peerSpec"}] - When I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + When I am in workspace "live" and dimension space point {"example":"source"} Then I expect the subgraph projection to consist of exactly 7 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -206,7 +206,7 @@ Feature: Create node peer variant | cs-identifier;elder-mc-nodeface;{"example":"source"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example":"peer"} + When I am in workspace "live" and dimension space point {"example":"peer"} Then I expect the subgraph projection to consist of exactly 8 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -271,7 +271,7 @@ Feature: Create node peer variant And I expect this node to have no succeeding siblings And I expect node aggregate identifier "invariable-mc-nodeface" and node path "document/invariable-document" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"example":"peerSpec"} + When I am in workspace "live" and dimension space point {"example":"peerSpec"} Then I expect the subgraph projection to consist of exactly 8 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -410,7 +410,7 @@ Feature: Create node peer variant And I expect a node identified by cs-identifier;youngest-mc-nodeface;{"example":"source"} to exist in the content graph And I expect a node identified by cs-identifier;invariable-mc-nodeface;{"example":"source"} to exist in the content graph - When I am in the active content stream of workspace "live" + 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 [{"example":"source"},{"example":"peer"},{"example":"peerSpec"}] @@ -443,7 +443,7 @@ Feature: Create node peer variant And I expect this node aggregate to occupy dimension space points [{"example":"source"}] And I expect this node aggregate to cover dimension space points [{"example":"source"}] - When I am in the active content stream of workspace "live" and dimension space point {"example":"peer"} + When I am in workspace "live" and dimension space point {"example":"peer"} Then I expect the subgraph projection to consist of exactly 5 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -473,7 +473,7 @@ Feature: Create node peer variant And I expect node aggregate identifier "youngest-mc-nodeface" and node path "youngest-document" to lead to no node And I expect node aggregate identifier "invariable-mc-nodeface" and node path "document/invariable-document" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"example": "peerSpec"} + When I am in workspace "live" and dimension space point {"example": "peerSpec"} Then I expect the subgraph projection to consist of exactly 5 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -577,7 +577,7 @@ Feature: Create node peer variant And I expect a node identified by cs-identifier;youngest-mc-nodeface;{"example":"source"} to exist in the content graph And I expect a node identified by cs-identifier;invariable-mc-nodeface;{"example":"source"} to exist in the content graph - When I am in the active content stream of workspace "live" + 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 [{"example":"source"},{"example":"peer"},{"example":"peerSpec"}] @@ -610,7 +610,7 @@ Feature: Create node peer variant And I expect this node aggregate to occupy dimension space points [{"example":"source"}] And I expect this node aggregate to cover dimension space points [{"example":"source"}] - When I am in the active content stream of workspace "live" and dimension space point {"example":"peer"} + When I am in workspace "live" and dimension space point {"example":"peer"} Then I expect the subgraph projection to consist of exactly 5 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -640,7 +640,7 @@ Feature: Create node peer variant And I expect node aggregate identifier "youngest-mc-nodeface" and node path "youngest-document" to lead to no node And I expect node aggregate identifier "invariable-mc-nodeface" and node path "document/invariable-document" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"example": "peerSpec"} + When I am in workspace "live" and dimension space point {"example": "peerSpec"} Then I expect the subgraph projection to consist of exactly 5 nodes Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -721,7 +721,7 @@ Feature: Create node peer variant And I expect a node identified by cs-identifier;youngest-mc-nodeface;{"example":"source"} to exist in the content graph And I expect a node identified by cs-identifier;invariable-mc-nodeface;{"example":"source"} to exist in the content graph - When I am in the active content stream of workspace "live" + 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 [{"example":"source"},{"example":"peer"},{"example":"peerSpec"}] @@ -754,7 +754,7 @@ Feature: Create node peer variant And I expect this node aggregate to occupy dimension space points [{"example":"source"}] And I expect this node aggregate to cover dimension space points [{"example":"source"}] - When I am in the active content stream of workspace "live" and dimension space point {"example":"peer"} + When I am in workspace "live" and dimension space point {"example":"peer"} Then I expect the subgraph projection to consist of exactly 4 nodes And I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -772,7 +772,7 @@ Feature: Create node peer variant | elder-child-document | cs-identifier;elder-child-mc-nodeface;{"example":"peer"} | And I expect node aggregate identifier "elder-child-mc-nodeface" and node path "elder-document/elder-child-document" to lead to node cs-identifier;elder-child-mc-nodeface;{"example":"peer"} - When I am in the active content stream of workspace "live" and dimension space point {"example": "peerSpec"} + When I am in workspace "live" and dimension space point {"example": "peerSpec"} Then I expect the subgraph projection to consist of exactly 4 nodes And I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/01-SetNodeProperties_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/01-SetNodeProperties_ConstraintChecks.feature index ab674a8bf39..9220aa46393 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/01-SetNodeProperties_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/01-SetNodeProperties_ConstraintChecks.feature @@ -26,7 +26,7 @@ Feature: Set node properties: Constraint checks | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/02-SetNodeProperties.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/02-SetNodeProperties.feature index a48915306a0..6f9cbbbfae8 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/02-SetNodeProperties.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/02-SetNodeProperties.feature @@ -64,7 +64,7 @@ Feature: Set properties | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"mul"} + And I am in workspace "live" and dimension space point {"language":"mul"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/03-SetNodeProperties_PropertyScopes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/03-SetNodeProperties_PropertyScopes.feature index 40fe2c65f85..e1efc50ef85 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/03-SetNodeProperties_PropertyScopes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/04-NodeModification/03-SetNodeProperties_PropertyScopes.feature @@ -37,7 +37,7 @@ Feature: Set node properties with different scopes | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"mul"} + And I am in workspace "live" and dimension space point {"language":"mul"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature index 2c4aefd3639..eb15cb9cc55 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/01-SetNodeReferences_ConstraintChecks.feature @@ -46,7 +46,7 @@ Feature: Constraint checks on SetNodeReferences | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/02-SetNodeReferences_WithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/02-SetNodeReferences_WithoutDimensions.feature index 287ab61f562..89e816585d1 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/02-SetNodeReferences_WithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/02-SetNodeReferences_WithoutDimensions.feature @@ -51,7 +51,7 @@ Feature: Node References without Dimensions | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/03-SetNodeReferences_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/03-SetNodeReferences_WithDimensions.feature index 466f0e67602..9e938dd1427 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/03-SetNodeReferences_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/03-SetNodeReferences_WithDimensions.feature @@ -30,7 +30,7 @@ Feature: Node References with Dimensions | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -49,7 +49,7 @@ Feature: Node References with Dimensions | references | [{"target": "anthony-destinode"}] | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "de"} And I expect this node to have the following references: | Name | Node | Properties | @@ -59,7 +59,7 @@ Feature: Node References with Dimensions | Name | Node | Properties | | referenceProperty | cs-identifier;source-nodandaise;{"language": "de"} | null | - When I am in the active content stream of workspace "live" and dimension space point {"language": "ch"} + When I am in workspace "live" and dimension space point {"language": "ch"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "de"} And I expect this node to have the following references: | Name | Node | Properties | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/04-SetNodeReferences_PropertyScopes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/04-SetNodeReferences_PropertyScopes.feature index 0bc4edb6565..2fea3b4a00f 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/04-SetNodeReferences_PropertyScopes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/04-SetNodeReferences_PropertyScopes.feature @@ -44,7 +44,7 @@ Feature: Set node properties with different scopes | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"mul"} + And I am in workspace "live" and dimension space point {"language":"mul"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -120,7 +120,7 @@ Feature: Set node properties with different scopes | references | [{"target": "anthony-destinode"}] | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"language": "mul"} + When I am in workspace "live" and dimension space point {"language": "mul"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "mul"} And I expect this node to have the following references: | Name | Node | Properties | @@ -133,7 +133,7 @@ Feature: Set node properties with different scopes | nodeAggregateScopedReference | cs-identifier;source-nodandaise;{"language": "mul"} | null | | nodeAggregateScopedReferences | cs-identifier;source-nodandaise;{"language": "mul"} | null | - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "de"} And I expect this node to have the following references: | Name | Node | Properties | @@ -157,7 +157,7 @@ Feature: Set node properties with different scopes | unscopedReference | cs-identifier;source-nodandaise;{"language": "de"} | null | | unscopedReferences | cs-identifier;source-nodandaise;{"language": "de"} | null | - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} + When I am in workspace "live" and dimension space point {"language": "gsw"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "gsw"} And I expect this node to have the following references: | Name | Node | Properties | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/05-NodeVariation_After_NodeReferencing.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/05-NodeVariation_After_NodeReferencing.feature index 35bfb620617..b09549fa2d9 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/05-NodeVariation_After_NodeReferencing.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/05-NodeReferencing/05-NodeVariation_After_NodeReferencing.feature @@ -30,7 +30,7 @@ Feature: Node References with Dimensions | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -57,7 +57,7 @@ Feature: Node References with Dimensions And the graph projection is fully up to date # after specialization, the reference must still exist on the specialized node - When I am in the active content stream of workspace "live" and dimension space point {"language": "ch"} + When I am in workspace "live" and dimension space point {"language": "ch"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "ch"} And I expect this node to have the following references: | Name | Node | Properties | @@ -68,7 +68,7 @@ Feature: Node References with Dimensions | referenceProperty | cs-identifier;source-nodandaise;{"language": "ch"} | null | # the reference must also exist on the non-touched nodes - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "de"} And I expect this node to have the following references: | Name | Node | Properties | @@ -89,7 +89,7 @@ Feature: Node References with Dimensions And the graph projection is fully up to date # reference to self (modified 2 lines above) - When I am in the active content stream of workspace "live" and dimension space point {"language": "ch"} + When I am in workspace "live" and dimension space point {"language": "ch"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "ch"} And I expect this node to have the following references: | Name | Node | Properties | @@ -99,7 +99,7 @@ Feature: Node References with Dimensions | referenceProperty | cs-identifier;source-nodandaise;{"language": "ch"} | null | # unmodified on the untouched nodes - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "de"} And I expect this node to have the following references: | Name | Node | Properties | @@ -127,7 +127,7 @@ Feature: Node References with Dimensions # on the specialization, the reference exists. - When I am in the active content stream of workspace "live" and dimension space point {"language": "ch"} + When I am in workspace "live" and dimension space point {"language": "ch"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "ch"} And I expect this node to have the following references: | Name | Node | Properties | @@ -138,7 +138,7 @@ Feature: Node References with Dimensions | referenceProperty | cs-identifier;source-nodandaise;{"language": "ch"} | null | # on the other nodes, the reference does not exist. - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "de"} And I expect this node to have no references @@ -170,7 +170,7 @@ Feature: Node References with Dimensions And the graph projection is fully up to date # after creating a peer, the reference must still exist on the peer node - When I am in the active content stream of workspace "live" and dimension space point {"language": "en"} + When I am in workspace "live" and dimension space point {"language": "en"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "en"} And I expect this node to have the following references: | Name | Node | Properties | @@ -181,7 +181,7 @@ Feature: Node References with Dimensions | referenceProperty | cs-identifier;source-nodandaise;{"language": "en"} | null | # the reference must also exist on the non-touched nodes - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "de"} And I expect this node to have the following references: | Name | Node | Properties | @@ -191,7 +191,7 @@ Feature: Node References with Dimensions | Name | Node | Properties | | referenceProperty | cs-identifier;source-nodandaise;{"language": "de"} | null | - When I am in the active content stream of workspace "live" and dimension space point {"language": "ch"} + When I am in workspace "live" and dimension space point {"language": "ch"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "de"} And I expect this node to have the following references: | Name | Node | Properties | @@ -212,7 +212,7 @@ Feature: Node References with Dimensions And the graph projection is fully up to date # reference to self (modified 2 lines above) - When I am in the active content stream of workspace "live" and dimension space point {"language": "en"} + When I am in workspace "live" and dimension space point {"language": "en"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "en"} And I expect this node to have the following references: | Name | Node | Properties | @@ -222,7 +222,7 @@ Feature: Node References with Dimensions | referenceProperty | cs-identifier;source-nodandaise;{"language": "en"} | null | # unmodified on the untouched nodes - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "de"} And I expect this node to have the following references: | Name | Node | Properties | @@ -232,7 +232,7 @@ Feature: Node References with Dimensions | Name | Node | Properties | | referenceProperty | cs-identifier;source-nodandaise;{"language": "de"} | null | - When I am in the active content stream of workspace "live" and dimension space point {"language": "ch"} + When I am in workspace "live" and dimension space point {"language": "ch"} Then I expect node aggregate identifier "source-nodandaise" to lead to node cs-identifier;source-nodandaise;{"language": "de"} And I expect this node to have the following references: | Name | Node | Properties | @@ -269,7 +269,7 @@ Feature: Node References with Dimensions And the graph projection is fully up to date # after generalizing, the reference must still exist on the generalized node - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "ch-only" to lead to node cs-identifier;ch-only;{"language": "de"} Then I expect this node to have the following references: | Name | Node | Properties | @@ -280,7 +280,7 @@ Feature: Node References with Dimensions | referenceProperty | cs-identifier;ch-only;{"language": "de"} | null | # the reference must also exist on the non-touched node - When I am in the active content stream of workspace "live" and dimension space point {"language": "ch"} + When I am in workspace "live" and dimension space point {"language": "ch"} Then I expect node aggregate identifier "ch-only" to lead to node cs-identifier;ch-only;{"language": "ch"} Then I expect this node to have the following references: | Name | Node | Properties | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/01-DisableNodeAggregate_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/01-DisableNodeAggregate_ConstraintChecks.feature index c1be0796564..573a37a422b 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/01-DisableNodeAggregate_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/01-DisableNodeAggregate_ConstraintChecks.feature @@ -23,7 +23,7 @@ Feature: Constraint checks on node aggregate disabling | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/02-DisableNodeAggregate_WithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/02-DisableNodeAggregate_WithoutDimensions.feature index ed23f5e23db..06a01480aaa 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/02-DisableNodeAggregate_WithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/02-DisableNodeAggregate_WithoutDimensions.feature @@ -24,7 +24,7 @@ Feature: Disable a node aggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -58,7 +58,7 @@ Feature: Disable a node aggregate | tag | "disabled" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Then I expect the graph projection to consist of exactly 5 nodes And I expect a node identified by cs-identifier;lady-eleonode-rootford;{} to exist in the content graph And I expect a node identified by cs-identifier;preceding-nodenborough;{} to exist in the content graph @@ -69,7 +69,7 @@ Feature: Disable a node aggregate And I expect the node aggregate "sir-david-nodenborough" to exist And I expect this node aggregate to disable dimension space points [{}] - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And VisibilityConstraints are set to "withoutRestrictions" Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/03-DisableNodeAggregate_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/03-DisableNodeAggregate_WithDimensions.feature index b4df608c120..6beb1bf8013 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/03-DisableNodeAggregate_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/03-DisableNodeAggregate_WithDimensions.feature @@ -26,7 +26,7 @@ Feature: Disable a node aggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"mul"} + And I am in workspace "live" and dimension space point {"language":"mul"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -68,7 +68,7 @@ Feature: Disable a node aggregate | tag | "disabled" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Then I expect the graph projection to consist of exactly 6 nodes And I expect a node identified by cs-identifier;lady-eleonode-rootford;{} to exist in the content graph And I expect a node identified by cs-identifier;preceding-nodenborough;{"language":"mul"} to exist in the content graph @@ -320,7 +320,7 @@ Feature: Disable a node aggregate | tag | "disabled" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Then I expect the graph projection to consist of exactly 6 nodes And I expect a node identified by cs-identifier;lady-eleonode-rootford;{} to exist in the content graph And I expect a node identified by cs-identifier;preceding-nodenborough;{"language":"mul"} to exist in the content graph diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/04-EnableNodeAggregate_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/04-EnableNodeAggregate_ConstraintChecks.feature index 311d3d0d487..deacbe55ea5 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/04-EnableNodeAggregate_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/04-EnableNodeAggregate_ConstraintChecks.feature @@ -23,7 +23,7 @@ Feature: Enable a node aggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/05-EnableNodeAggregate_WithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/05-EnableNodeAggregate_WithoutDimensions.feature index b44170f3870..e684962577c 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/05-EnableNodeAggregate_WithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/05-EnableNodeAggregate_WithoutDimensions.feature @@ -24,7 +24,7 @@ Feature: Enable a node aggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -64,7 +64,7 @@ Feature: Enable a node aggregate | tag | "disabled" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Then I expect the graph projection to consist of exactly 5 nodes And I expect a node identified by cs-identifier;lady-eleonode-rootford;{} to exist in the content graph And I expect a node identified by cs-identifier;preceding-nodenborough;{} to exist in the content graph @@ -75,7 +75,7 @@ Feature: Enable a node aggregate And I expect the node aggregate "sir-david-nodenborough" to exist And I expect this node aggregate to disable dimension space points [] - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And VisibilityConstraints are set to "frontend" Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -146,14 +146,14 @@ Feature: Enable a node aggregate | tag | "disabled" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Then I expect the node aggregate "sir-david-nodenborough" to exist And I expect this node aggregate to disable dimension space points [] And I expect the node aggregate "nody-mc-nodeface" to exist And I expect this node aggregate to disable dimension space points [{}] - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And VisibilityConstraints are set to "frontend" Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: @@ -224,14 +224,14 @@ Feature: Enable a node aggregate | tag | "disabled" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Then I expect the node aggregate "sir-david-nodenborough" to exist And I expect this node aggregate to disable dimension space points [{}] And I expect the node aggregate "nody-mc-nodeface" to exist And I expect this node aggregate to disable dimension space points [] - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And VisibilityConstraints are set to "frontend" Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have the following child nodes: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/06-EnableNodeAggregate_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/06-EnableNodeAggregate_WithDimensions.feature index 97f56b109d3..6ea2bb37452 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/06-EnableNodeAggregate_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/06-EnableNodeAggregate_WithDimensions.feature @@ -26,7 +26,7 @@ Feature: Enable a node aggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"mul"} + And I am in workspace "live" and dimension space point {"language":"mul"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -82,7 +82,7 @@ Feature: Enable a node aggregate | tag | "disabled" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Then I expect the graph projection to consist of exactly 7 nodes And I expect a node identified by cs-identifier;lady-eleonode-rootford;{} to exist in the content graph And I expect a node identified by cs-identifier;preceding-nodenborough;{"language":"mul"} to exist in the content graph @@ -378,7 +378,7 @@ Feature: Enable a node aggregate | tag | "disabled" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Then I expect the graph projection to consist of exactly 7 nodes And I expect a node identified by cs-identifier;lady-eleonode-rootford;{} to exist in the content graph And I expect a node identified by cs-identifier;preceding-nodenborough;{"language":"mul"} to exist in the content graph @@ -706,7 +706,7 @@ Feature: Enable a node aggregate | nodeVariantSelectionStrategy | "allVariants" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Then I expect the node aggregate "the-great-nodini" to exist And I expect this node aggregate to disable dimension space points [] diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/07-CreateNodeAggregateWithNodeWithDisabledAncestor_WithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/07-CreateNodeAggregateWithNodeWithDisabledAncestor_WithoutDimensions.feature index 1d236a45a38..a2cd0880fb1 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/07-CreateNodeAggregateWithNodeWithDisabledAncestor_WithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/07-CreateNodeAggregateWithNodeWithDisabledAncestor_WithoutDimensions.feature @@ -22,7 +22,7 @@ Feature: Creation of nodes underneath disabled nodes | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/08-CreateNodeAggregateWithNodeWithDisabledAncestor_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/08-CreateNodeAggregateWithNodeWithDisabledAncestor_WithDimensions.feature index 48ca1fa841d..f566fe2fb78 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/08-CreateNodeAggregateWithNodeWithDisabledAncestor_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/08-CreateNodeAggregateWithNodeWithDisabledAncestor_WithDimensions.feature @@ -24,7 +24,7 @@ Feature: Creation of nodes underneath disabled nodes | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"mul"} + And I am in workspace "live" and dimension space point {"language":"mul"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/09-CreateNodeVariantOfDisabledNode.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/09-CreateNodeVariantOfDisabledNode.feature index d7acb6a6c92..02b888a19df 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/09-CreateNodeVariantOfDisabledNode.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/06-NodeDisabling/09-CreateNodeVariantOfDisabledNode.feature @@ -22,7 +22,7 @@ Feature: Variation of hidden nodes | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"mul"} + And I am in workspace "live" and dimension space point {"language":"mul"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/01-RemoveNodeAggregate_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/01-RemoveNodeAggregate_ConstraintChecks.feature index 301e1937186..d40ecf604c5 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/01-RemoveNodeAggregate_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/01-RemoveNodeAggregate_ConstraintChecks.feature @@ -27,7 +27,7 @@ Feature: Remove NodeAggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/02-RemoveNodeAggregate_WithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/02-RemoveNodeAggregate_WithoutDimensions.feature index 96d5fdbf148..a1f9d63b75a 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/02-RemoveNodeAggregate_WithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/02-RemoveNodeAggregate_WithoutDimensions.feature @@ -24,7 +24,7 @@ Feature: Remove NodeAggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/03-RemoveNodeAggregate_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/03-RemoveNodeAggregate_WithDimensions.feature index 7039cbfc02b..38f12dc934a 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/03-RemoveNodeAggregate_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/03-RemoveNodeAggregate_WithDimensions.feature @@ -26,7 +26,7 @@ Feature: Remove NodeAggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"en"} + And I am in workspace "live" and dimension space point {"language":"en"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/04-VariantRecreation.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/04-VariantRecreation.feature index 81027a506e7..d128cf5a5b6 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/04-VariantRecreation.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/04-VariantRecreation.feature @@ -32,7 +32,7 @@ Feature: Recreate a node variant | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"en"} + And I am in workspace "live" and dimension space point {"language":"en"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -94,7 +94,7 @@ Feature: Recreate a node variant | targetOrigin | {"language":"de"} | And the graph projection is fully up to date - When I am in the active content stream of workspace "user-ws" and dimension space point {"language": "de"} + When I am in workspace "user-ws" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" and node path "document" to lead to node new-user-cs-id;sir-david-nodenborough;{"language": "de"} Then I expect node aggregate identifier "nodimus-prime" and node path "document/tethered-document" to lead to node new-user-cs-id;nodimus-prime;{"language": "de"} Then I expect node aggregate identifier "nodimus-mediocre" and node path "document/tethered-document/tethered" to lead to node new-user-cs-id;nodimus-mediocre;{"language": "de"} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/05-CreateNodeAfterDeletion.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/05-CreateNodeAfterDeletion.feature index e8402d7025e..8a7c075a96e 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/05-CreateNodeAfterDeletion.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/05-CreateNodeAfterDeletion.feature @@ -31,7 +31,7 @@ Feature: Create node specialization | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And I am in workspace "live" and dimension space point {"example":"source"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -69,7 +69,7 @@ Feature: Create node specialization And I expect a node identified by cs-identifier;younger-mc-nodeface;{"example":"source"} to exist in the content graph And I expect a node identified by cs-identifier;youngest-mc-nodeface;{"example":"source"} to exist in the content graph - When I am in the active content stream of workspace "live" + 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 [{"example":"source"},{"example":"spec"},{"example":"leafSpec"}] diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/06-CreateNodeSpecializationVariantAfterDeletion.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/06-CreateNodeSpecializationVariantAfterDeletion.feature index a0c1ae951a9..ffb3aceb16c 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/06-CreateNodeSpecializationVariantAfterDeletion.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/07-NodeRemoval/06-CreateNodeSpecializationVariantAfterDeletion.feature @@ -32,7 +32,7 @@ Feature: Create node specialization | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And I am in workspace "live" and dimension space point {"example":"source"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -79,7 +79,7 @@ Feature: Create node specialization And I expect a node identified by cs-identifier;younger-mc-nodeface;{"example":"source"} to exist in the content graph And I expect a node identified by cs-identifier;youngest-mc-nodeface;{"example":"source"} to exist in the content graph - When I am in the active content stream of workspace "live" + 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 [{"example":"source"},{"example":"spec"},{"example":"leafSpec"}] diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature index 721f5a667ec..5aa384d386b 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/01-MoveNodes_ConstraintChecks.feature @@ -36,7 +36,7 @@ Feature: Move node to a new parent / within the current parent before a sibling | workspaceTitle | "Live" | | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + And I am in workspace "live" and dimension space point {"example": "source"} And the graph projection is fully up to date And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NoNewParent_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NoNewParent_Dimensions.feature index 906fe2c3ed7..3be7125e4bf 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NoNewParent_Dimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/02-MoveNodeAggregate_NoNewParent_Dimensions.feature @@ -25,7 +25,7 @@ Feature: Move a node with content dimensions | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I am in workspace "live" and dimension space point {"example": "general"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -59,7 +59,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "eldest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -70,7 +70,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -81,7 +81,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to have no preceding siblings And I expect this node to have the following succeeding siblings: @@ -91,7 +91,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to have no preceding siblings And I expect this node to have the following succeeding siblings: @@ -126,7 +126,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "eldest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -137,7 +137,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -148,7 +148,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -158,7 +158,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -194,7 +194,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -206,7 +206,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -218,7 +218,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -228,7 +228,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -258,7 +258,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -270,7 +270,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -282,7 +282,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -294,7 +294,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -331,7 +331,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "younger-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -343,7 +343,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -355,7 +355,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -366,7 +366,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -403,7 +403,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -415,7 +415,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -427,7 +427,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -439,7 +439,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -469,7 +469,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -480,7 +480,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -491,7 +491,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -502,7 +502,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -538,7 +538,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -549,7 +549,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -560,7 +560,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -570,7 +570,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -606,7 +606,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -618,7 +618,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -630,7 +630,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -640,7 +640,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -670,7 +670,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -682,7 +682,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -694,7 +694,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -706,7 +706,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -743,7 +743,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -755,7 +755,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -767,7 +767,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -778,7 +778,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -815,7 +815,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -827,7 +827,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -839,7 +839,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -851,7 +851,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -883,7 +883,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "eldest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -895,7 +895,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -906,7 +906,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to have no preceding siblings And I expect this node to have the following succeeding siblings: @@ -916,7 +916,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | @@ -952,7 +952,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -964,7 +964,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -975,7 +975,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -985,7 +985,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1022,7 +1022,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1034,7 +1034,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1046,7 +1046,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -1056,7 +1056,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1086,7 +1086,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1098,7 +1098,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1110,7 +1110,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1122,7 +1122,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1159,7 +1159,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "younger-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1171,7 +1171,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1183,7 +1183,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1194,7 +1194,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1231,7 +1231,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1243,7 +1243,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1255,7 +1255,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1267,7 +1267,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1297,7 +1297,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1309,7 +1309,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1320,7 +1320,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1331,7 +1331,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1368,7 +1368,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1380,7 +1380,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1391,7 +1391,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1401,7 +1401,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1438,7 +1438,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1450,7 +1450,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1462,7 +1462,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1472,7 +1472,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1502,7 +1502,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1514,7 +1514,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1526,7 +1526,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1538,7 +1538,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1575,7 +1575,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1587,7 +1587,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1599,7 +1599,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1610,7 +1610,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1647,7 +1647,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1659,7 +1659,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1671,7 +1671,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1683,7 +1683,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1715,7 +1715,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1727,7 +1727,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -1738,7 +1738,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | @@ -1749,7 +1749,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to have the following preceding siblings: | NodeDiscriminator | @@ -1789,7 +1789,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1801,7 +1801,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -1811,7 +1811,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1822,7 +1822,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1852,7 +1852,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1864,7 +1864,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1876,7 +1876,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1888,7 +1888,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1926,7 +1926,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1938,7 +1938,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1949,7 +1949,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1961,7 +1961,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1998,7 +1998,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2010,7 +2010,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2020,7 +2020,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2031,7 +2031,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2068,7 +2068,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2080,7 +2080,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2090,7 +2090,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2101,7 +2101,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2131,7 +2131,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2143,7 +2143,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2155,7 +2155,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2167,7 +2167,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document/child-document-n" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature index 77a395b3f64..216e0251cfb 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/03-MoveNodeAggregate_NewParent_Dimensions.feature @@ -25,7 +25,7 @@ Feature: Move a node with content dimensions | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I am in workspace "live" and dimension space point {"example": "general"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -64,7 +64,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "eldest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -75,7 +75,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -86,7 +86,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -97,7 +97,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -133,7 +133,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "eldest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -144,7 +144,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -155,7 +155,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -165,7 +165,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -201,7 +201,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -213,7 +213,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -225,7 +225,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -235,7 +235,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -265,7 +265,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -277,7 +277,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -289,7 +289,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -301,7 +301,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -336,7 +336,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "younger-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -348,7 +348,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -360,7 +360,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -371,7 +371,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -408,7 +408,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": "youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -420,7 +420,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -432,7 +432,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -442,7 +442,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -472,7 +472,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -483,7 +483,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -494,7 +494,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -505,7 +505,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -541,7 +541,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId": null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -552,7 +552,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -563,7 +563,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -573,7 +573,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -609,7 +609,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId":"youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -621,7 +621,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -633,7 +633,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -643,7 +643,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -673,7 +673,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId":"youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -685,7 +685,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -697,7 +697,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -709,7 +709,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -746,7 +746,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId":"youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -758,7 +758,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -770,7 +770,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -781,7 +781,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -818,7 +818,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId":"elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null},{"dimensionSpacePoint":{"example":"peer"},"nodeAggregateId":"elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -830,7 +830,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -842,7 +842,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} # The given preceding sibling cannot be resolved and since elder-mc-nodeface isn't given as a succeeding sibling, the node is moved at the end @@ -853,7 +853,7 @@ Feature: Move a node with content dimensions | cs-identifier;elder-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} And I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -885,7 +885,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "eldest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -895,7 +895,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -906,7 +906,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -917,7 +917,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -952,7 +952,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "eldest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -962,7 +962,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -973,7 +973,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -983,7 +983,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1018,7 +1018,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1028,7 +1028,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1040,7 +1040,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -1050,7 +1050,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1078,7 +1078,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1088,7 +1088,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1100,7 +1100,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1112,7 +1112,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1147,7 +1147,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId": "elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId": "younger-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1157,7 +1157,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1169,7 +1169,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1180,7 +1180,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1215,7 +1215,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1225,7 +1225,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1237,7 +1237,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1247,7 +1247,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1275,7 +1275,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1285,7 +1285,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1296,7 +1296,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1307,7 +1307,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1342,7 +1342,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1352,7 +1352,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1363,7 +1363,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1373,7 +1373,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1408,7 +1408,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1418,7 +1418,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1430,7 +1430,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1440,7 +1440,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1468,7 +1468,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1478,7 +1478,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1490,7 +1490,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1502,7 +1502,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1537,7 +1537,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"youngest-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1547,7 +1547,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1559,7 +1559,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1570,7 +1570,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1605,7 +1605,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"elder-mc-nodeface"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1615,7 +1615,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1627,7 +1627,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1638,7 +1638,7 @@ Feature: Move a node with content dimensions | cs-identifier;elder-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1668,7 +1668,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"eldest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1678,7 +1678,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -1689,7 +1689,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1699,7 +1699,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1738,7 +1738,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1748,7 +1748,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1758,7 +1758,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have no preceding siblings @@ -1768,7 +1768,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1796,7 +1796,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"elder-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1806,7 +1806,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1818,7 +1818,7 @@ Feature: Move a node with content dimensions | cs-identifier;younger-mc-nodeface;{"example": "general"} | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1828,7 +1828,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1864,7 +1864,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1874,7 +1874,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1885,7 +1885,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1895,7 +1895,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1930,7 +1930,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1940,7 +1940,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1950,7 +1950,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1960,7 +1960,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -1995,7 +1995,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2005,7 +2005,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2015,7 +2015,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2025,7 +2025,7 @@ Feature: Move a node with content dimensions | cs-identifier;eldest-mc-nodeface;{"example": "general"} | And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2053,7 +2053,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-david-nodenborough" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"youngest-mc-nodeface"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2063,7 +2063,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2073,7 +2073,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;source-younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2085,7 +2085,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;youngest-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -2128,5 +2128,5 @@ Feature: Move a node with content dimensions | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"general"},"nodeAggregateId":null}] | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} And I expect node aggregate identifier "nody-mc-nodeface-ii" to lead to node cs-identifier;nody-mc-nodeface-ii;{"example": "general"} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ScatteredChildren.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ScatteredChildren.feature index 151909239af..da8bd43063f 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ScatteredChildren.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/04-MoveNodeAggregate_ScatteredChildren.feature @@ -22,7 +22,7 @@ Feature: Move a node with content dimensions | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I am in workspace "live" and dimension space point {"example": "general"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -68,7 +68,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "sir-nodeward-nodington-iii" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"bustling-destinode"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"younger-destinode"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -78,7 +78,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -90,7 +90,7 @@ Feature: Move a node with content dimensions | cs-identifier;bustling-destinode;{"example": "general"} | | cs-identifier;younger-destinode;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -101,7 +101,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;younger-destinode;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -137,7 +137,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | "bustling-destinode" | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":null},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":null}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -147,19 +147,19 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;younger-mc-nodeface;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/bustling-target-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;bustling-destinode;{"example": "general"} And I expect this node to have no preceding siblings And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/bustling-target-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;bustling-destinode;{"example": "general"} And I expect this node to have no preceding siblings And I expect this node to have no succeeding siblings - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -195,7 +195,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"elder-destinode"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -206,7 +206,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;younger-destinode;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -217,7 +217,7 @@ Feature: Move a node with content dimensions | cs-identifier;elder-destinode;{"example": "general"} | | cs-identifier;younger-destinode;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/esquire-child/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;nodimus-mediocre;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -227,7 +227,7 @@ Feature: Move a node with content dimensions | NodeDiscriminator | | cs-identifier;younger-child-destinode;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "bustling-destinode" and node path "esquire/bustling-target-document" to lead to node cs-identifier;bustling-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -273,7 +273,7 @@ Feature: Move a node with content dimensions | newParentNodeAggregateId | null | | succeedingSiblingsForCoverage | [{"dimensionSpacePoint":{"example":"source"},"nodeAggregateId":"bustling-destinode"},{"dimensionSpacePoint":{"example":"spec"},"nodeAggregateId":"bustling-destinode"}] | - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "elder-destinode" and node path "esquire/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -284,7 +284,7 @@ Feature: Move a node with content dimensions | cs-identifier;bustling-destinode;{"example": "general"} | | cs-identifier;younger-destinode;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "elder-destinode" and node path "esquire/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -295,7 +295,7 @@ Feature: Move a node with content dimensions | cs-identifier;bustling-destinode;{"example": "general"} | | cs-identifier;younger-destinode;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "elder-destinode" and node path "esquire/esquire-child/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;nodimus-mediocre;{"example": "general"} And I expect this node to have the following preceding siblings: @@ -306,7 +306,7 @@ Feature: Move a node with content dimensions | cs-identifier;bustling-destinode;{"example": "general"} | | cs-identifier;younger-child-destinode;{"example": "general"} | - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "elder-destinode" and node path "esquire/elder-target-document" to lead to node cs-identifier;elder-destinode;{"example": "general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example": "general"} And I expect this node to have the following preceding siblings: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature index ea431988e7d..06d9bc48849 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/05-MoveNodeAggregate_SubtreeTags.feature @@ -28,7 +28,7 @@ Feature: Move a node aggregate into and out of a tagged parent | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I am in workspace "live" and dimension space point {"example": "general"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -60,7 +60,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -71,7 +71,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -82,7 +82,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -93,7 +93,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -121,7 +121,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -132,7 +132,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -143,7 +143,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -154,7 +154,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -184,7 +184,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -195,7 +195,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -206,7 +206,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -217,7 +217,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -245,7 +245,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -256,7 +256,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -267,7 +267,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -278,7 +278,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -309,7 +309,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -320,7 +320,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -331,7 +331,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -342,7 +342,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -371,7 +371,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -382,7 +382,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -393,7 +393,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -404,7 +404,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -443,7 +443,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -454,7 +454,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -465,7 +465,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -476,7 +476,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -513,7 +513,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -524,7 +524,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -535,7 +535,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -546,7 +546,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -583,7 +583,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -594,7 +594,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -605,7 +605,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -616,7 +616,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -653,7 +653,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -664,7 +664,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -675,7 +675,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -686,7 +686,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -723,7 +723,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -734,7 +734,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -745,7 +745,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1,tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -756,7 +756,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1,tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -793,7 +793,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -804,7 +804,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -815,7 +815,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -826,7 +826,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1,tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -863,7 +863,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -874,7 +874,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -885,7 +885,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -896,7 +896,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1,tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -933,7 +933,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -944,7 +944,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -955,7 +955,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -966,7 +966,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1,tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1005,7 +1005,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1016,7 +1016,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -1027,7 +1027,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -1038,7 +1038,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1075,7 +1075,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1086,7 +1086,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -1097,7 +1097,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -1108,7 +1108,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1145,7 +1145,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1156,7 +1156,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1167,7 +1167,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -1178,7 +1178,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1215,7 +1215,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1226,7 +1226,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1237,7 +1237,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -1248,7 +1248,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1285,7 +1285,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1296,7 +1296,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -1307,7 +1307,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1,tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -1318,7 +1318,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1,tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1355,7 +1355,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1366,7 +1366,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -1377,7 +1377,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -1388,7 +1388,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1,tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1425,7 +1425,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1436,7 +1436,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1447,7 +1447,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -1458,7 +1458,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1,tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1495,7 +1495,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1506,7 +1506,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1517,7 +1517,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "tag1" @@ -1528,7 +1528,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1,tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1559,7 +1559,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1570,7 +1570,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1581,7 +1581,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1592,7 +1592,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1621,7 +1621,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1632,7 +1632,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1643,7 +1643,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-nodeward-nodington-iii;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1654,7 +1654,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1693,7 +1693,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1704,7 +1704,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1715,7 +1715,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1726,7 +1726,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1763,7 +1763,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1774,7 +1774,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1785,7 +1785,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1796,7 +1796,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1833,7 +1833,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1844,7 +1844,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1855,7 +1855,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1866,7 +1866,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1903,7 +1903,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1914,7 +1914,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1925,7 +1925,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1936,7 +1936,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1973,7 +1973,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1984,7 +1984,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -1995,7 +1995,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2006,7 +2006,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2043,7 +2043,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2054,7 +2054,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2065,7 +2065,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2076,7 +2076,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2113,7 +2113,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2124,7 +2124,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2135,7 +2135,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2146,7 +2146,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2183,7 +2183,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2194,7 +2194,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2205,7 +2205,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2216,7 +2216,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2255,7 +2255,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2266,7 +2266,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2277,7 +2277,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2288,7 +2288,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2325,7 +2325,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2336,7 +2336,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2347,7 +2347,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2358,7 +2358,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2395,7 +2395,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2406,7 +2406,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2417,7 +2417,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2428,7 +2428,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2465,7 +2465,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2476,7 +2476,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2487,7 +2487,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2498,7 +2498,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag1" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2535,7 +2535,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2546,7 +2546,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2557,7 +2557,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2568,7 +2568,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2605,7 +2605,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2616,7 +2616,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2627,7 +2627,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2638,7 +2638,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2675,7 +2675,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2686,7 +2686,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2697,7 +2697,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2708,7 +2708,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2745,7 +2745,7 @@ Feature: Move a node aggregate into and out of a tagged parent | relationDistributionStrategy | "gatherSpecializations" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2756,7 +2756,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2767,7 +2767,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "" - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "esquire/esquire-child/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;nodimus-prime;{"example":"general"} And I expect this node to be exactly explicitly tagged "" @@ -2778,7 +2778,7 @@ Feature: Move a node aggregate into and out of a tagged parent And I expect this node to be exactly explicitly tagged "" And I expect this node to exactly inherit the tags "tag2" - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} And I expect this node to be a child of node cs-identifier;sir-david-nodenborough;{"example":"general"} And I expect this node to be exactly explicitly tagged "" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature index a401fe2a00d..d869cae7195 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/06-AdditionalConstraintChecks.feature @@ -18,7 +18,7 @@ Feature: Additional constraint checks after move node capabilities are introduce | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + And I am in workspace "live" and dimension space point {"example": "general"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/07-MoveNodeAggregateWithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/07-MoveNodeAggregateWithoutDimensions.feature index 42efb17c2fa..fbde6f92b7a 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/07-MoveNodeAggregateWithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/08-NodeMove/07-MoveNodeAggregateWithoutDimensions.feature @@ -24,7 +24,7 @@ Feature: Move a node without content dimensions | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -78,7 +78,7 @@ Feature: Move a node without content dimensions # node aggregate occupation and coverage is not relevant without dimensions and thus not tested - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-nodeward-nodington-iii" and node path "esquire" to lead to node cs-identifier;sir-nodeward-nodington-iii;{} And I expect this node to be a child of node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no preceding siblings @@ -113,7 +113,7 @@ Feature: Move a node without content dimensions # node aggregate occupation and coverage is not relevant without dimensions and thus not tested - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-nodeward-nodington-iii" and node path "esquire" to lead to node cs-identifier;sir-nodeward-nodington-iii;{} And I expect this node to be a child of node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no preceding siblings @@ -167,7 +167,7 @@ Feature: Move a node without content dimensions # node aggregate occupation and coverage is not relevant without dimensions and thus not tested - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And I expect node aggregate identifier "sir-nodeward-nodington-iii" and node path "esquire" to lead to node cs-identifier;sir-nodeward-nodington-iii;{} And I expect this node to be a child of node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no preceding siblings @@ -213,7 +213,7 @@ Feature: Move a node without content dimensions # node aggregate occupation and coverage is not relevant without dimensions and thus not tested - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And I expect node aggregate identifier "sir-david-nodenborough" and node path "document" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to be a child of node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no preceding siblings diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/01-ForkContentStream_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/01-ForkContentStream_ConstraintChecks.feature index 8d25d71e03a..ff0f58bdd9a 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/01-ForkContentStream_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/01-ForkContentStream_ConstraintChecks.feature @@ -24,7 +24,7 @@ Feature: ForkContentStream Without Dimensions | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/ForkContentStreamWithDisabledNodesWithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/ForkContentStreamWithDisabledNodesWithoutDimensions.feature index dcc5fad2036..c4a13c11db4 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/ForkContentStreamWithDisabledNodesWithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/ForkContentStreamWithDisabledNodesWithoutDimensions.feature @@ -23,7 +23,7 @@ Feature: On forking a content stream, hidden nodes should be correctly copied as | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/ForkContentStreamWithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/ForkContentStreamWithoutDimensions.feature index e7b8197080c..357c6406deb 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/ForkContentStreamWithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/ForkContentStreamWithoutDimensions.feature @@ -24,7 +24,7 @@ Feature: ForkContentStream Without Dimensions | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/NodeReferencesOnForkContentStream.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/NodeReferencesOnForkContentStream.feature index e8b6e164bc4..75decf3a791 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/NodeReferencesOnForkContentStream.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ContentStreamForking/NodeReferencesOnForkContentStream.feature @@ -29,7 +29,7 @@ Feature: On forking a content stream, node references should be copied as well. | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/AddDimensionShineThrough.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/AddDimensionShineThrough.feature index b8e3d750c59..ef2f1eaf151 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/AddDimensionShineThrough.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/AddDimensionShineThrough.feature @@ -72,7 +72,7 @@ Feature: Add Dimension Specialization to: { language: 'ch' } """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" + When I am in workspace "live" And I am in dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} And I expect this node to be of type "Neos.ContentRepository.Testing:Document" @@ -124,12 +124,12 @@ Feature: Add Dimension Specialization | text | "changed" | # the original content stream was untouched - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} And I expect this node to have the following properties: | Key | Value | | text | "hello" | - When I am in the active content stream of workspace "live" and dimension space point {"language": "ch"} + When I am in workspace "live" and dimension space point {"language": "ch"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to no node When I run integrity violation detection @@ -145,7 +145,7 @@ Feature: Add Dimension Specialization And the graph projection is fully up to date # ensure the node is disabled - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to no node When VisibilityConstraints are set to "withoutRestrictions" Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/AddNewProperty_NoDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/AddNewProperty_NoDimensions.feature index e99b20f0e24..9d47c6e453a 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/AddNewProperty_NoDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/AddNewProperty_NoDimensions.feature @@ -23,7 +23,7 @@ Feature: Add New Property | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -75,7 +75,7 @@ Feature: Add New Property type: 'DateTime' """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/ChangePropertyValue_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/ChangePropertyValue_Dimensions.feature index bd52c8d1d2b..e219bf8c2c8 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/ChangePropertyValue_Dimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/ChangePropertyValue_Dimensions.feature @@ -29,7 +29,7 @@ Feature: Change Property Value across dimensions; and test DimensionSpacePoints | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -79,19 +79,19 @@ Feature: Change Property Value across dimensions; and test DimensionSpacePoints # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} And I expect this node to have the following properties: | Key | Value | | text | "Original text" | - When I am in the active content stream of workspace "live" and dimension space point {"language": "ch"} + When I am in workspace "live" and dimension space point {"language": "ch"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} And I expect this node to have the following properties: | Key | Value | | text | "Original text" | - When I am in the active content stream of workspace "live" and dimension space point {"language": "en"} + When I am in workspace "live" and dimension space point {"language": "en"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "en"} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/ChangePropertyValue_NoDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/ChangePropertyValue_NoDimensions.feature index 2c3ae2d9969..abfb634b4ec 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/ChangePropertyValue_NoDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/ChangePropertyValue_NoDimensions.feature @@ -24,7 +24,7 @@ Feature: Change Property | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -59,7 +59,7 @@ Feature: Change Property newSerializedValue: 'fixed value' """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/Filter_NodeName_NoDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/Filter_NodeName_NoDimensions.feature index 0958d6cf1f4..0c3a24f7559 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/Filter_NodeName_NoDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/Filter_NodeName_NoDimensions.feature @@ -23,7 +23,7 @@ Feature: Filter - Node Name | workspaceTitle | "Live" | | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the graph projection is fully up to date And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | @@ -81,7 +81,7 @@ Feature: Filter - Node Name newSerializedValue: 'fixed value' """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "na-name1" to lead to node cs-identifier;na-name1;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/Filter_PropertyNotEmpty_NoDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/Filter_PropertyNotEmpty_NoDimensions.feature index 103e8290d27..87c80dbd804 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/Filter_PropertyNotEmpty_NoDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/Filter_PropertyNotEmpty_NoDimensions.feature @@ -24,7 +24,7 @@ Feature: Filter - Property not empty | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -91,7 +91,7 @@ Feature: Filter - Property not empty newSerializedValue: 'fixed value' """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "na-name1" to lead to node cs-identifier;na-name1;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/Filter_PropertyValue_NoDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/Filter_PropertyValue_NoDimensions.feature index b3e314fab70..a3469927d45 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/Filter_PropertyValue_NoDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/Filter_PropertyValue_NoDimensions.feature @@ -23,7 +23,7 @@ Feature: Filter - Property Value | workspaceTitle | "Live" | | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the graph projection is fully up to date And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | @@ -92,7 +92,7 @@ Feature: Filter - Property Value newSerializedValue: 'fixed value' """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "na-name1" to lead to node cs-identifier;na-name1;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/MoveDimensionSpacePoint.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/MoveDimensionSpacePoint.feature index c3a56b5605b..cba219a40f8 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/MoveDimensionSpacePoint.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/MoveDimensionSpacePoint.feature @@ -35,7 +35,7 @@ Feature: Move dimension space point | workspaceTitle | "Live" | | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the graph projection is fully up to date And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | @@ -96,7 +96,7 @@ Feature: Move dimension space point And the graph projection is fully up to date # ensure the node is disabled - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to no node When VisibilityConstraints are set to "withoutRestrictions" Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} @@ -120,7 +120,7 @@ Feature: Move dimension space point """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to no node When VisibilityConstraints are set to "withoutRestrictions" Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/NodeTypeAdjustment_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/NodeTypeAdjustment_Dimensions.feature index b4394b7679f..dd77fc64c91 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/NodeTypeAdjustment_Dimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/NodeTypeAdjustment_Dimensions.feature @@ -30,7 +30,7 @@ Feature: Adjust node types with a node migration | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -75,11 +75,11 @@ Feature: Adjust node types with a node migration newType: 'Neos.ContentRepository.Testing:OtherDocument' """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} And I expect this node to be of type "Neos.ContentRepository.Testing:Document" # ... also in the fallback dimension - When I am in the active content stream of workspace "live" and dimension space point {"language": "ch"} + When I am in workspace "live" and dimension space point {"language": "ch"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} And I expect this node to be of type "Neos.ContentRepository.Testing:Document" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/NodeTypeAdjustment_NoDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/NodeTypeAdjustment_NoDimensions.feature index afd94e15f65..bd8bc50105e 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/NodeTypeAdjustment_NoDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/NodeTypeAdjustment_NoDimensions.feature @@ -28,7 +28,7 @@ Feature: Adjust node types with a node migration | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -73,7 +73,7 @@ Feature: Adjust node types with a node migration newType: 'Neos.ContentRepository.Testing:OtherDocument' """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to be of type "Neos.ContentRepository.Testing:Document" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RemoveNodes_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RemoveNodes_Dimensions.feature index 9498e574b49..2c03aa7d782 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RemoveNodes_Dimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RemoveNodes_Dimensions.feature @@ -27,7 +27,7 @@ Feature: Remove Nodes | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -72,13 +72,13 @@ Feature: Remove Nodes type: 'RemoveNode' """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} + When I am in workspace "live" and dimension space point {"language": "gsw"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} - When I am in the active content stream of workspace "live" and dimension space point {"language": "en"} + When I am in workspace "live" and dimension space point {"language": "en"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "en"} # the node was removed inside the new content stream, but only in de and gsw (virtual specialization) @@ -118,13 +118,13 @@ Feature: Remove Nodes """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} + When I am in workspace "live" and dimension space point {"language": "gsw"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} - When I am in the active content stream of workspace "live" and dimension space point {"language": "en"} + When I am in workspace "live" and dimension space point {"language": "en"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "en"} # the node was removed inside the new content stream, but only in de and gsw, since it is a specialization @@ -188,13 +188,13 @@ Feature: Remove Nodes """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} + When I am in workspace "live" and dimension space point {"language": "gsw"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} - When I am in the active content stream of workspace "live" and dimension space point {"language": "en"} + When I am in workspace "live" and dimension space point {"language": "en"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "en"} # the node was removed inside the new content stream, but only in gsw @@ -229,13 +229,13 @@ Feature: Remove Nodes """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} + When I am in workspace "live" and dimension space point {"language": "gsw"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} - When I am in the active content stream of workspace "live" and dimension space point {"language": "en"} + When I am in workspace "live" and dimension space point {"language": "en"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "en"} # the node was removed inside the new content stream, but only in gsw @@ -275,13 +275,13 @@ Feature: Remove Nodes """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} + When I am in workspace "live" and dimension space point {"language": "gsw"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} - When I am in the active content stream of workspace "live" and dimension space point {"language": "en"} + When I am in workspace "live" and dimension space point {"language": "en"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "en"} # the node was removed inside the new content stream, but only in gsw @@ -313,13 +313,13 @@ Feature: Remove Nodes """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} - When I am in the active content stream of workspace "live" and dimension space point {"language": "gsw"} + When I am in workspace "live" and dimension space point {"language": "gsw"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "de"} - When I am in the active content stream of workspace "live" and dimension space point {"language": "en"} + When I am in workspace "live" and dimension space point {"language": "en"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language": "en"} # the node was removed inside the new content stream, but only in gsw diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RemoveProperty_NoDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RemoveProperty_NoDimensions.feature index 399bcf84a1d..ef9a9cbe68f 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RemoveProperty_NoDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RemoveProperty_NoDimensions.feature @@ -24,7 +24,7 @@ Feature: Remove Property | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -58,7 +58,7 @@ Feature: Remove Property property: 'text' """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RenameNodeAggregate_Dimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RenameNodeAggregate_Dimensions.feature index 2842fc39a2d..b222db6b1f1 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RenameNodeAggregate_Dimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RenameNodeAggregate_Dimensions.feature @@ -27,7 +27,7 @@ Feature: Rename Node Aggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -72,10 +72,10 @@ Feature: Rename Node Aggregate # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + When I am in workspace "live" and dimension space point {"language": "de"} Then I expect the node "sir-david-nodenborough" to have the name "foo" - When I am in the active content stream of workspace "live" and dimension space point {"language": "ch"} + When I am in workspace "live" and dimension space point {"language": "ch"} Then I expect the node "sir-david-nodenborough" to have the name "foo" # the node was changed inside the new content stream, across all dimensions diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RenameProperty_NoDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RenameProperty_NoDimensions.feature index b63569bd20f..1856aacb601 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RenameProperty_NoDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/RenameProperty_NoDimensions.feature @@ -24,7 +24,7 @@ Feature: Rename Property | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -72,7 +72,7 @@ Feature: Rename Property to: 'newText' """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/StripTagsOnProperty_NoDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/StripTagsOnProperty_NoDimensions.feature index 987cbc19287..7ebff06074d 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/StripTagsOnProperty_NoDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/EventSourced/Migration/StripTagsOnProperty_NoDimensions.feature @@ -24,7 +24,7 @@ Feature: Strip Tags on Property | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -58,7 +58,7 @@ Feature: Strip Tags on Property property: 'text' """ # the original content stream has not been touched - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeCopying/CopyNode_NoDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeCopying/CopyNode_NoDimensions.feature index 28634b325b5..32177b21410 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeCopying/CopyNode_NoDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeCopying/CopyNode_NoDimensions.feature @@ -16,7 +16,7 @@ Feature: Copy nodes (without dimensions) | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -54,7 +54,7 @@ Feature: Copy nodes (without dimensions) And the graph projection is fully up to date Scenario: Copy - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} # node to copy (currentNode): "sir-nodeward-nodington-iii" Then I expect node aggregate identifier "sir-nodeward-nodington-iii" to lead to node cs-identifier;sir-nodeward-nodington-iii;{} When the command CopyNodesRecursively is executed, copying the current node aggregate with payload: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodePropertyConversion/NodePropertyConversion.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodePropertyConversion/NodePropertyConversion.feature index 77aa2b318ad..cdab8e20bfd 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodePropertyConversion/NodePropertyConversion.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodePropertyConversion/NodePropertyConversion.feature @@ -17,7 +17,7 @@ Feature: Node Property Conversion | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -35,7 +35,7 @@ Feature: Node Property Conversion And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{} And I expect this node to have the following properties: | Key | Value | @@ -58,7 +58,7 @@ Feature: Node Property Conversion | propertyValues | {"dateProperty": "Date:1997-07-19T19:20:30+05:00"} | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRemoval/RemoveNodeAggregateAfterDisabling.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRemoval/RemoveNodeAggregateAfterDisabling.feature index f6355585dc0..7ce7deadf6a 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRemoval/RemoveNodeAggregateAfterDisabling.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRemoval/RemoveNodeAggregateAfterDisabling.feature @@ -24,7 +24,7 @@ Feature: Disable a node aggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -65,7 +65,7 @@ Feature: Disable a node aggregate | nodeName | "child-document" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Then I expect the graph projection to consist of exactly 5 nodes And I expect a node identified by cs-identifier;lady-eleonode-rootford;{} to exist in the content graph And I expect a node identified by cs-identifier;preceding-nodenborough;{} to exist in the content graph @@ -76,7 +76,7 @@ Feature: Disable a node aggregate And I expect the node aggregate "sir-david-nodenborough" to exist And I expect this node aggregate to disable dimension space points [] - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And VisibilityConstraints are set to "frontend" Then the subtree for node aggregate "lady-eleonode-rootford" with node types "" and 2 levels deep should be: | Level | nodeAggregateId | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRemoval/RemoveNodeAggregateWithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRemoval/RemoveNodeAggregateWithDimensions.feature index 9f44a74c9c0..ae829e65373 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRemoval/RemoveNodeAggregateWithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRemoval/RemoveNodeAggregateWithDimensions.feature @@ -20,7 +20,7 @@ Feature: Remove NodeAggregate | workspaceDescription | "The live workspace" | | newContentStreamId | "live-cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-nodesworth" | @@ -65,11 +65,11 @@ Feature: Remove NodeAggregate Then I expect the graph projection to consist of exactly 1 node And I expect a node identified by live-cs-identifier;lady-eleonode-nodesworth;{} to exist in the content graph - When I am in content stream "live-cs-identifier" and dimension space point {"language":"de"} + When I am in workspace "live" and dimension space point {"language":"de"} Then I expect the subgraph projection to consist of exactly 1 nodes And I expect node aggregate identifier "lady-eleonode-nodesworth" to lead to node live-cs-identifier;lady-eleonode-nodesworth;{} - When I am in content stream "live-cs-identifier" and dimension space point {"language":"gsw"} + When I am in workspace "live" and dimension space point {"language":"gsw"} Then I expect the subgraph projection to consist of exactly 1 nodes And I expect node aggregate identifier "lady-eleonode-nodesworth" to lead to node live-cs-identifier;lady-eleonode-nodesworth;{} @@ -82,7 +82,7 @@ Feature: Remove NodeAggregate | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "user-test" + And I am in workspace "user-test" When the command RemoveNodeAggregate is executed with payload: | Key | Value | @@ -97,22 +97,22 @@ Feature: Remove NodeAggregate And I expect a node identified by live-cs-identifier;nody-mc-nodeface;{"language":"gsw"} to exist in the content graph And I expect a node identified by live-cs-identifier;nodimus-prime;{"language":"de"} to exist in the content graph - When I am in content stream "user-cs-identifier" and dimension space point {"language":"de"} + When I am in workspace "user-test" and dimension space point {"language":"de"} Then I expect the subgraph projection to consist of exactly 1 nodes And I expect node aggregate identifier "lady-eleonode-nodesworth" to lead to node user-cs-identifier;lady-eleonode-nodesworth;{} - When I am in content stream "user-cs-identifier" and dimension space point {"language":"gsw"} + When I am in workspace "user-test" and dimension space point {"language":"gsw"} Then I expect the subgraph projection to consist of exactly 1 nodes And I expect node aggregate identifier "lady-eleonode-nodesworth" to lead to node user-cs-identifier;lady-eleonode-nodesworth;{} # ensure LIVE ContentStream is untouched - When I am in content stream "live-cs-identifier" and dimension space point {"language":"de"} + When I am in workspace "live" and dimension space point {"language":"de"} Then I expect the subgraph projection to consist of exactly 3 nodes And I expect node aggregate identifier "lady-eleonode-nodesworth" to lead to node live-cs-identifier;lady-eleonode-nodesworth;{} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document" to lead to node live-cs-identifier;nody-mc-nodeface;{"language":"de"} And I expect node aggregate identifier "nodimus-prime" and node path "document/child-document" to lead to node live-cs-identifier;nodimus-prime;{"language":"de"} - When I am in content stream "live-cs-identifier" and dimension space point {"language":"gsw"} + When I am in workspace "live" and dimension space point {"language":"gsw"} Then I expect the subgraph projection to consist of exactly 3 nodes And I expect node aggregate identifier "lady-eleonode-nodesworth" to lead to node live-cs-identifier;lady-eleonode-nodesworth;{} And I expect node aggregate identifier "nody-mc-nodeface" and node path "document" to lead to node live-cs-identifier;nody-mc-nodeface;{"language":"gsw"} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature index fe721eb01a0..94856b0145f 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/01_ChangeNodeAggregateName_ConstraintChecks.feature @@ -24,7 +24,7 @@ Feature: Change node name | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/ChangeNodeAggregateName.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/ChangeNodeAggregateName.feature index e1760070c4e..52b9f0e276d 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/ChangeNodeAggregateName.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRenaming/ChangeNodeAggregateName.feature @@ -18,7 +18,7 @@ Feature: Change node name | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | @@ -64,7 +64,7 @@ Feature: Change node name | nodeAggregateClassification | "regular" | And the graph projection is fully up to date # we read the node initially, to ensure it is filled in the cache (to check whether cache clearing actually works) - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "lady-eleonode-rootford" to lead to node cs-identifier;lady-eleonode-rootford;{} Then I expect this node to have the following child nodes: | Name | NodeDiscriminator | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature index 9414ce67343..3671ab9210b 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_BasicErrorCases.feature @@ -50,7 +50,7 @@ Feature: Change node aggregate type - basic error cases | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_DeleteStrategy.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_DeleteStrategy.feature index c2bfccea819..26f82f07947 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_DeleteStrategy.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_DeleteStrategy.feature @@ -66,7 +66,7 @@ Feature: Change node aggregate type - behavior of DELETE strategy | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -101,13 +101,13 @@ Feature: Change node aggregate type - behavior of DELETE strategy And the graph projection is fully up to date # the type has changed - When I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + When I am in workspace "live" and dimension space point {"language":"de"} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{"language":"de"} And I expect this node to be of type "Neos.ContentRepository.Testing:ParentNodeTypeB" # the child nodes have been removed Then I expect node aggregate identifier "nody-mc-nodeface" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"language":"gsw"} + When I am in workspace "live" and dimension space point {"language":"gsw"} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to no node Scenario: Try to change to a node type that disallows already present grandchildren with the delete conflict resolution strategy @@ -138,19 +138,19 @@ Feature: Change node aggregate type - behavior of DELETE strategy And the graph projection is fully up to date # the type has changed - When I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + When I am in workspace "live" and dimension space point {"language":"de"} Then I expect node aggregate identifier "parent2-na" to lead to node cs-identifier;parent2-na;{"language":"de"} And I expect this node to be of type "Neos.ContentRepository.Testing:ParentNodeTypeB" # the child nodes still exist - When I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + When I am in workspace "live" and dimension space point {"language":"de"} Then I expect node aggregate identifier "autocreated-child" to lead to node cs-identifier;autocreated-child;{"language":"de"} - When I am in the active content stream of workspace "live" and dimension space point {"language":"gsw"} + When I am in workspace "live" and dimension space point {"language":"gsw"} Then I expect node aggregate identifier "autocreated-child" to lead to node cs-identifier;autocreated-child;{"language":"de"} # the grandchild nodes have been removed Then I expect node aggregate identifier "nody-mc-nodeface" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"language":"gsw"} + When I am in workspace "live" and dimension space point {"language":"gsw"} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to no node @@ -181,11 +181,11 @@ Feature: Change node aggregate type - behavior of DELETE strategy And the graph projection is fully up to date # the type has changed - When I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + When I am in workspace "live" and dimension space point {"language":"de"} Then I expect node aggregate identifier "nodea-identifier-de" to lead to node cs-identifier;nodea-identifier-de;{"language":"de"} And I expect this node to be of type "Neos.ContentRepository.Testing:NodeTypeB" - When I am in the active content stream of workspace "live" and dimension space point {"language":"gsw"} + When I am in workspace "live" and dimension space point {"language":"gsw"} Then I expect node aggregate identifier "nodea-identifier-de" to lead to node cs-identifier;nodea-identifier-de;{"language":"gsw"} And I expect this node to be of type "Neos.ContentRepository.Testing:NodeTypeB" And I expect this node to have the following child nodes: @@ -220,7 +220,7 @@ Feature: Change node aggregate type - behavior of DELETE strategy And the graph projection is fully up to date # the type has changed - When I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + When I am in workspace "live" and dimension space point {"language":"de"} Then I expect node aggregate identifier "nodea-identifier-de" to lead to node cs-identifier;nodea-identifier-de;{"language":"de"} And I expect this node to be of type "Neos.ContentRepository.Testing:NodeTypeB" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_HappyPathStrategy.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_HappyPathStrategy.feature index cf130d3c988..07a3e086e20 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_HappyPathStrategy.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeRetyping/ChangeNodeAggregateType_HappyPathStrategy.feature @@ -59,7 +59,7 @@ Feature: Change node aggregate type - behavior of HAPPYPATH strategy | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -147,11 +147,11 @@ Feature: Change node aggregate type - behavior of HAPPYPATH strategy And the graph projection is fully up to date # the type has changed - When I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + When I am in workspace "live" and dimension space point {"language":"de"} Then I expect node aggregate identifier "nodea-identifier-de" to lead to node cs-identifier;nodea-identifier-de;{"language":"de"} And I expect this node to be of type "Neos.ContentRepository.Testing:NodeTypeB" - When I am in the active content stream of workspace "live" and dimension space point {"language":"gsw"} + When I am in workspace "live" and dimension space point {"language":"gsw"} Then I expect node aggregate identifier "nodea-identifier-de" to lead to node cs-identifier;nodea-identifier-de;{"language":"gsw"} And I expect this node to be of type "Neos.ContentRepository.Testing:NodeTypeB" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature index db876fac357..2af6ab93c19 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/AncestorNodes.feature @@ -51,7 +51,7 @@ Feature: Find and count nodes using the findAncestorNodes and countAncestorNodes | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/ChildNodes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/ChildNodes.feature index 565e2556cc6..c842280f9be 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/ChildNodes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/ChildNodes.feature @@ -65,7 +65,7 @@ Feature: Find and count nodes using the findChildNodes and countChildNodes queri | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/ClosestNode.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/ClosestNode.feature index c5816b0d300..22b1ef2eb29 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/ClosestNode.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/ClosestNode.feature @@ -51,7 +51,7 @@ Feature: Find nodes using the findClosestNode query | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/CountNodes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/CountNodes.feature index 33d104675a4..4f2530014ce 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/CountNodes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/CountNodes.feature @@ -53,7 +53,7 @@ Feature: Find nodes using the countNodes query | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/DescendantNodes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/DescendantNodes.feature index 34847488825..d3dd853dcb1 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/DescendantNodes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/DescendantNodes.feature @@ -65,7 +65,7 @@ Feature: Find and count nodes using the findDescendantNodes and countDescendantN | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeById.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeById.feature index b61dffbc52f..3891287536c 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeById.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeById.feature @@ -64,7 +64,7 @@ Feature: Find nodes using the findNodeById query | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeByPath.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeByPath.feature index c895ac33025..0b5fd8d9ab6 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeByPath.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeByPath.feature @@ -67,7 +67,7 @@ Feature: Find nodes using the findNodeByPath query | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeByPathAsNodeName.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeByPathAsNodeName.feature index bb98926abc2..93e2a7d9d6b 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeByPathAsNodeName.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindNodeByPathAsNodeName.feature @@ -64,7 +64,7 @@ Feature: Find nodes using the findNodeByPath query with node name as path argume | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindParentNode.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindParentNode.feature index 68bc44ef005..c830b3ceaef 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindParentNode.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindParentNode.feature @@ -64,7 +64,7 @@ Feature: Find nodes using the findParentNodes query | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindRootNodeByType.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindRootNodeByType.feature index c56e42a84a0..e74a0ba467a 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindRootNodeByType.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindRootNodeByType.feature @@ -24,7 +24,7 @@ Feature: Find root nodes by type | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindSubtree.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindSubtree.feature index f65d2abd4ce..1df9aaf8820 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindSubtree.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/FindSubtree.feature @@ -53,7 +53,7 @@ Feature: Find nodes using the findSubtree query | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/References.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/References.feature index 13a8e586444..488bb7f4bc4 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/References.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/References.feature @@ -74,7 +74,7 @@ Feature: Find and count references and their target nodes using the findReferenc | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/RetrieveNodePath.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/RetrieveNodePath.feature index d4bf8631090..e90515a436b 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/RetrieveNodePath.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/RetrieveNodePath.feature @@ -54,7 +54,7 @@ Feature: Find nodes using the retrieveNodePath query | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/SiblingNodes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/SiblingNodes.feature index a94e992bd83..bf88395dede 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/SiblingNodes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/SiblingNodes.feature @@ -64,7 +64,7 @@ Feature: Find sibling nodes using the findPrecedingSiblingNodes and findSucceedi | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature index ed4672f8e85..de114fdd5f1 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature @@ -75,7 +75,7 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | newContentStreamId | "cs-user" | | workspaceOwner | "some-user" | And the graph projection is fully up to date - And I am in the active content stream of workspace "user-test" and dimension space point {"language":"de"} + And I am in workspace "user-test" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -103,12 +103,12 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | nodeAggregateId | "a" | | propertyValues | {"text": "Changed"} | And the graph projection is fully up to date - And I am in content stream "cs-user" and dimension space point {"language":"de"} + And I am in workspace "user-test" and dimension space point {"language":"de"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | | | - When I am in content stream "cs-user" and dimension space point {"language":"ch"} + When I am in workspace "user-test" and dimension space point {"language":"ch"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:30:00 | 2023-03-16 12:30:00 | 2023-03-16 13:00:00 | 2023-03-16 13:00:00 | @@ -121,12 +121,12 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | nodeAggregateId | "a" | | newNodeName | "a-renamed" | And the graph projection is fully up to date - And I am in content stream "cs-user" and dimension space point {"language":"de"} + And I am in workspace "user-test" and dimension space point {"language":"de"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | 2023-03-16 13:00:00 | 2023-03-16 13:00:00 | - When I am in content stream "cs-user" and dimension space point {"language":"ch"} + When I am in workspace "user-test" and dimension space point {"language":"ch"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:30:00 | 2023-03-16 12:30:00 | 2023-03-16 13:00:00 | 2023-03-16 13:00:00 | @@ -141,7 +141,7 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | referenceName | "ref" | | references | [{"target": "b"}] | And the graph projection is fully up to date - And I am in content stream "cs-user" and dimension space point {"language":"de"} + And I am in workspace "user-test" and dimension space point {"language":"de"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | | | @@ -149,7 +149,7 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | | | - When I am in content stream "cs-user" and dimension space point {"language":"ch"} + When I am in workspace "user-test" and dimension space point {"language":"ch"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:30:00 | 2023-03-16 12:30:00 | 2023-03-16 13:00:00 | 2023-03-16 13:00:00 | @@ -166,12 +166,12 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | newNodeTypeName | "Neos.ContentRepository.Testing:SpecialPage" | | strategy | "happypath" | And the graph projection is fully up to date - And I am in content stream "cs-user" and dimension space point {"language":"de"} + And I am in workspace "user-test" and dimension space point {"language":"de"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | 2023-03-16 13:00:00 | 2023-03-16 13:00:00 | - When I am in content stream "cs-user" and dimension space point {"language":"ch"} + When I am in workspace "user-test" and dimension space point {"language":"ch"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:30:00 | 2023-03-16 12:30:00 | 2023-03-16 13:00:00 | 2023-03-16 13:00:00 | @@ -184,12 +184,12 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | sourceOrigin | {"language":"de"} | | targetOrigin | {"language":"en"} | And the graph projection is fully up to date - And I am in content stream "cs-user" and dimension space point {"language":"de"} + And I am in workspace "user-test" and dimension space point {"language":"de"} Then I expect the node "home" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | | | - When I am in content stream "cs-user" and dimension space point {"language":"en"} + When I am in workspace "user-test" and dimension space point {"language":"en"} Then I expect the node "home" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 13:00:00 | 2023-03-16 13:00:00 | | | @@ -202,12 +202,12 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | sourceOrigin | {"language":"de"} | | targetOrigin | {"language":"mul"} | And the graph projection is fully up to date - And I am in content stream "cs-user" and dimension space point {"language":"de"} + And I am in workspace "user-test" and dimension space point {"language":"de"} Then I expect the node "home" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | | | - When I am in content stream "cs-user" and dimension space point {"language":"mul"} + When I am in workspace "user-test" and dimension space point {"language":"mul"} Then I expect the node "home" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 13:00:00 | 2023-03-16 13:00:00 | | | @@ -223,12 +223,12 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | nodeAggregateId | "a" | | newParentNodeAggregateId | "b" | And the graph projection is fully up to date - And I am in content stream "cs-user" and dimension space point {"language":"de"} + And I am in workspace "user-test" and dimension space point {"language":"de"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | | | - When I am in content stream "cs-user" and dimension space point {"language":"ch"} + When I am in workspace "user-test" and dimension space point {"language":"ch"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:30:00 | 2023-03-16 12:30:00 | | | @@ -239,12 +239,12 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | And the graph projection is fully up to date - And I am in content stream "cs-user" and dimension space point {"language":"de"} + And I am in workspace "user-test" and dimension space point {"language":"de"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | | | - When I am in content stream "cs-user" and dimension space point {"language":"ch"} + When I am in workspace "user-test" and dimension space point {"language":"ch"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:30:00 | 2023-03-16 12:30:00 | | | @@ -258,13 +258,13 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | nodeAggregateId | "a" | | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date - And I am in content stream "cs-user" and dimension space point {"language":"de"} + And I am in workspace "user-test" and dimension space point {"language":"de"} And VisibilityConstraints are set to "withoutRestrictions" Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | | | - When I am in content stream "cs-user" and dimension space point {"language":"ch"} + When I am in workspace "user-test" and dimension space point {"language":"ch"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:30:00 | 2023-03-16 12:30:00 | | | @@ -277,12 +277,12 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | nodeAggregateId | "a" | | nodeVariantSelectionStrategy | "allSpecializations" | And the graph projection is fully up to date - And I am in content stream "cs-user" and dimension space point {"language":"de"} + And I am in workspace "user-test" and dimension space point {"language":"de"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | | | - When I am in content stream "cs-user" and dimension space point {"language":"ch"} + When I am in workspace "user-test" and dimension space point {"language":"ch"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 12:30:00 | 2023-03-16 12:30:00 | | | @@ -303,15 +303,15 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | workspaceName | "user-test" | And the graph projection is fully up to date - And I am in content stream "cs-user" + And I am in workspace "user-test" Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | - | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | 2023-03-16 13:00:00 | 2023-03-16 13:00:00 | + | 2023-03-16 14:00:00 | 2023-03-16 12:00:00 | 2023-03-16 14:00:00 | 2023-03-16 13:00:00 | And I expect the node "b" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | - | 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | | | + | 2023-03-16 14:00:00 | 2023-03-16 12:00:00 | | | - And I am in content stream "cs-review" + And I am in workspace "review" Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 14:00:00 | 2023-03-16 12:00:00 | 2023-03-16 14:00:00 | 2023-03-16 13:00:00 | @@ -324,7 +324,7 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | Key | Value | | workspaceName | "review" | And the graph projection is fully up to date - And I am in content stream "cs-live" + And I am in workspace "live" Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 15:00:00 | 2023-03-16 12:00:00 | 2023-03-16 15:00:00 | 2023-03-16 13:00:00 | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesCoverTheirOrigin.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesCoverTheirOrigin.feature index f7c96024ca4..adea8b499c5 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesCoverTheirOrigin.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/AllNodesCoverTheirOrigin.feature @@ -20,7 +20,7 @@ Feature: Run projection integrity violation detection to find nodes that do not | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/NodeAggregateIdentifiersAreUniquePerSubgraph.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/NodeAggregateIdentifiersAreUniquePerSubgraph.feature index cb0fa7092c4..1d25c75ec68 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/NodeAggregateIdentifiersAreUniquePerSubgraph.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/NodeAggregateIdentifiersAreUniquePerSubgraph.feature @@ -20,7 +20,7 @@ Feature: Create two nodes with the same node aggregate identifier in the same su | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/NodeAggregatesAreConsistentlyClassifiedPerContentStream.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/NodeAggregatesAreConsistentlyClassifiedPerContentStream.feature index 79752e4371e..607bcde6626 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/NodeAggregatesAreConsistentlyClassifiedPerContentStream.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/NodeAggregatesAreConsistentlyClassifiedPerContentStream.feature @@ -20,7 +20,7 @@ Feature: Run projection integrity violation detection regarding node aggregate c | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/NodeAggregatesAreConsistentlyTypedPerContentStream.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/NodeAggregatesAreConsistentlyTypedPerContentStream.feature index 5ddfd4d3e0a..af45832048d 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/NodeAggregatesAreConsistentlyTypedPerContentStream.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/NodeAggregatesAreConsistentlyTypedPerContentStream.feature @@ -22,7 +22,7 @@ Feature: Run projection integrity violation detection regarding node aggregate t | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/ReferenceIntegrityIsProvided.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/ReferenceIntegrityIsProvided.feature index f8101ef42c1..56093467cf7 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/ReferenceIntegrityIsProvided.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/ProjectionIntegrityViolationDetection/ReferenceIntegrityIsProvided.feature @@ -20,7 +20,7 @@ Feature: Run integrity violation detection regarding reference relations | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + And I am in workspace "live" and dimension space point {"language":"de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/RootNodeAggregateDimensionUpdates/UpdateRootNodeAggregateDimensions_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/RootNodeAggregateDimensionUpdates/UpdateRootNodeAggregateDimensions_WithDimensions.feature index 3382f6122a5..b18e736a678 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/RootNodeAggregateDimensionUpdates/UpdateRootNodeAggregateDimensions_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/RootNodeAggregateDimensionUpdates/UpdateRootNodeAggregateDimensions_WithDimensions.feature @@ -20,7 +20,7 @@ Feature: Update Root Node aggregate dimensions | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/DimensionMismatch.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/DimensionMismatch.feature index b059a1cf922..21d602c5891 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/DimensionMismatch.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/DimensionMismatch.feature @@ -25,7 +25,7 @@ Feature: Dimension mismatch | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language": "en"} + And I am in workspace "live" and dimension space point {"language": "en"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/DisallowedChildNode.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/DisallowedChildNode.feature index d662515984e..8272d781e12 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/DisallowedChildNode.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/DisallowedChildNode.feature @@ -31,7 +31,7 @@ Feature: Remove disallowed Child Nodes and grandchild nodes | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -85,7 +85,7 @@ Feature: Remove disallowed Child Nodes and grandchild nodes When I adjust the node structure for node type "Neos.ContentRepository.Testing:Document" Then I expect no needed structure adjustments for type "Neos.ContentRepository.Testing:Document" - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And I expect node aggregate identifier "sir-david-nodenborough" to lead to no node @@ -121,7 +121,7 @@ Feature: Remove disallowed Child Nodes and grandchild nodes | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -184,6 +184,6 @@ Feature: Remove disallowed Child Nodes and grandchild nodes When I adjust the node structure for node type "Neos.ContentRepository.Testing:SubDocument" Then I expect no needed structure adjustments for type "Neos.ContentRepository.Testing:SubDocument" - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And I expect node aggregate identifier "subdoc" to lead to no node diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/DisallowedChildNodesAndTetheredNodes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/DisallowedChildNodesAndTetheredNodes.feature index 9b819a633ee..8fcc7e9a361 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/DisallowedChildNodesAndTetheredNodes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/DisallowedChildNodesAndTetheredNodes.feature @@ -31,7 +31,7 @@ Feature: Remove disallowed Child Nodes and grandchild nodes | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/Properties.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/Properties.feature index ce752a421b1..24952a84ece 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/Properties.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/Properties.feature @@ -25,7 +25,7 @@ Feature: Properties | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -57,7 +57,7 @@ Feature: Properties When I adjust the node structure for node type "Neos.ContentRepository.Testing:Document" Then I expect no needed structure adjustments for type "Neos.ContentRepository.Testing:Document" - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have no properties @@ -79,7 +79,7 @@ Feature: Properties When I adjust the node structure for node type "Neos.ContentRepository.Testing:Document" Then I expect no needed structure adjustments for type "Neos.ContentRepository.Testing:Document" - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | @@ -131,6 +131,6 @@ Feature: Properties When I adjust the node structure for node type "Neos.ContentRepository.Testing:Document" Then I expect no needed structure adjustments for type "Neos.ContentRepository.Testing:Document" - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have no properties diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/TetheredNodes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/TetheredNodes.feature index e5ef4bb624b..de6d9e0673c 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/TetheredNodes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/TetheredNodes.feature @@ -37,7 +37,7 @@ Feature: Tethered Nodes integrity violations | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -144,7 +144,7 @@ Feature: Tethered Nodes integrity violations When I adjust the node structure for node type "Neos.ContentRepository:Root" Then I expect no needed structure adjustments for type "Neos.ContentRepository:Root" - When I am in the active content stream of workspace "live" and dimension space point {"market":"CH", "language":"gsw"} + When I am in workspace "live" and dimension space point {"market":"CH", "language":"gsw"} And I get the node at path "document/some-new-child" And I expect this node to have the following properties: | Key | Value | @@ -203,7 +203,7 @@ Feature: Tethered Nodes integrity violations Then I expect no needed structure adjustments for type "Neos.ContentRepository.Testing:Document" When I adjust the node structure for node type "Neos.ContentRepository:Root" Then I expect no needed structure adjustments for type "Neos.ContentRepository:Root" - When I am in the active content stream of workspace "live" and dimension space point {"market":"CH", "language":"gsw"} + When I am in workspace "live" and dimension space point {"market":"CH", "language":"gsw"} Then I expect node aggregate identifier "nodewyn-tetherton" to lead to no node Then I expect node aggregate identifier "nodimer-tetherton" to lead to no node And I expect path "tethered-node" to lead to no node diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/TetheredNodesReordering.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/TetheredNodesReordering.feature index 72bc102776d..2e6b41fe1ce 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/TetheredNodesReordering.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/TetheredNodesReordering.feature @@ -26,7 +26,7 @@ Feature: Tethered Nodes Reordering Structure changes | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -71,7 +71,7 @@ Feature: Tethered Nodes Reordering Structure changes When I adjust the node structure for node type "Neos.ContentRepository.Testing:Document" Then I expect no needed structure adjustments for type "Neos.ContentRepository.Testing:Document" - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And I get the node at path "document/tethered-node" And I expect this node to have the following preceding siblings: | NodeDiscriminator | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/UnknownNodeType.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/UnknownNodeType.feature index 6bcb3ca90ff..5006a854579 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/UnknownNodeType.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/StructureAdjustment/UnknownNodeType.feature @@ -18,7 +18,7 @@ Feature: Unknown node types | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -47,6 +47,6 @@ Feature: Unknown node types When I adjust the node structure for node type "Neos.ContentRepository.Testing:Document" Then I expect no needed structure adjustments for type "Neos.ContentRepository.Testing:Document" - When I am in the active content stream of workspace "live" and dimension space point {"market":"CH", "language":"gsw"} + When I am in workspace "live" and dimension space point {"market":"CH", "language":"gsw"} And I expect node aggregate identifier "sir-david-nodenborough" to lead to no node diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithDimensions.feature index 6da6d7e9020..4b80d2f1960 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithDimensions.feature @@ -23,7 +23,7 @@ Feature: Tag subtree with dimensions | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "root" | @@ -180,7 +180,7 @@ Feature: Tag subtree with dimensions | sourceOrigin | {"language":"de"} | | targetOrigin | {"language":"gsw"} | And the graph projection is fully up to date - And I am in the active content stream of workspace "user-ws" and dimension space point {"language":"gsw"} + And I am in workspace "user-ws" and dimension space point {"language":"gsw"} And I execute the findSubtree query for entry node aggregate id "a" I expect the following tree with tags: """ a (tag1*) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature index 6dc797cfa53..5273addf313 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/SubtreeTagging/TagSubtree_WithoutDimensions.feature @@ -21,7 +21,7 @@ Feature: Tag subtree without dimensions | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "root" | @@ -108,10 +108,10 @@ Feature: Tag subtree without dimensions | tag | "tag1" | When the graph projection is fully up to date - And I am in content stream "cs-identifier" + And I am in workspace "live" Then I expect the graph projection to consist of exactly 12 nodes - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect the node with aggregate identifier "a1" to be explicitly tagged "tag1" Then I expect the node with aggregate identifier "a1a" to inherit the tag "tag1" Then I expect the node with aggregate identifier "a1a1" to inherit the tag "tag1" diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W10-IndividualNodeDiscarding/01-ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W10-IndividualNodeDiscarding/01-ConstraintChecks.feature index 271e9440ced..3614ab4af0f 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W10-IndividualNodeDiscarding/01-ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W10-IndividualNodeDiscarding/01-ConstraintChecks.feature @@ -30,7 +30,7 @@ Feature: Workspace discarding - complex chained functionality | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + And I am in workspace "live" and dimension space point {"language": "de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -84,5 +84,5 @@ Feature: Workspace discarding - complex chained functionality | workspaceName | "user-ws" | | newContentStreamId | "user-cs-id-yet-again-rebased" | And the graph projection is fully up to date - When I am in the active content stream of workspace "user-ws" and dimension space point {"language": "de"} + When I am in workspace "user-ws" and dimension space point {"language": "de"} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-id-yet-again-rebased;nody-mc-nodeface;{"language": "de"} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W10-IndividualNodeDiscarding/02-BasicFeatures.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W10-IndividualNodeDiscarding/02-BasicFeatures.feature index 7698343042a..b79d8cc1789 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W10-IndividualNodeDiscarding/02-BasicFeatures.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W10-IndividualNodeDiscarding/02-BasicFeatures.feature @@ -30,7 +30,7 @@ Feature: Discard individual nodes (basics) | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -108,7 +108,7 @@ Feature: Discard individual nodes (basics) And the graph projection is fully up to date - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier-new;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | @@ -130,7 +130,7 @@ Feature: Discard individual nodes (basics) | newContentStreamId | "user-cs-identifier-new" | And the graph projection is fully up to date - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier-new;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | @@ -152,7 +152,7 @@ Feature: Discard individual nodes (basics) | newContentStreamId | "user-cs-identifier-new" | And the graph projection is fully up to date - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier-new;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | @@ -175,7 +175,7 @@ Feature: Discard individual nodes (basics) And the graph projection is fully up to date # live WS does not change because of a discard - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/02-RebasingWithAutoCreatedNodes.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/02-RebasingWithAutoCreatedNodes.feature index cd071f56e12..3460bc39efd 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/02-RebasingWithAutoCreatedNodes.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/02-RebasingWithAutoCreatedNodes.feature @@ -33,7 +33,7 @@ Feature: Rebasing auto-created nodes works | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the graph projection is fully up to date And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | @@ -59,7 +59,7 @@ Feature: Rebasing auto-created nodes works | originDimensionSpacePoint | {} | | parentNodeAggregateId | "lady-eleonode-rootford" | And the graph projection is fully up to date - And I am in content stream "user-cs-identifier" and dimension space point {} + And I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-identifier;nody-mc-nodeface;{} When I get the node at path "mcnodeface/foo" And I expect this node to be a child of node user-cs-identifier;nody-mc-nodeface;{} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/03-RebasingWithConflictingChanges.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/03-RebasingWithConflictingChanges.feature index f47658abbf1..6d656e26dcd 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/03-RebasingWithConflictingChanges.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W6-WorkspaceRebasing/03-RebasingWithConflictingChanges.feature @@ -23,7 +23,7 @@ Feature: Workspace rebasing - conflicting changes | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W7-WorkspacePublication/02-PublishWorkspace.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W7-WorkspacePublication/02-PublishWorkspace.feature index ce52bbcb253..eeeec62bf8c 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W7-WorkspacePublication/02-PublishWorkspace.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W7-WorkspacePublication/02-PublishWorkspace.feature @@ -23,7 +23,7 @@ Feature: Workspace based content publishing | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -91,13 +91,13 @@ Feature: Workspace based content publishing | propertyValues | {"text": "Modified"} | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{} And I expect this node to have the following properties: | Key | Value | | text | "Original" | - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-identifier;nody-mc-nodeface;{} And I expect this node to have the following properties: | Key | Value | @@ -109,7 +109,7 @@ Feature: Workspace based content publishing | workspaceName | "user-test" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{} And I expect this node to have the following properties: | Key | Value | @@ -151,7 +151,7 @@ Feature: Workspace based content publishing | workspaceName | "user-test" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{} And I expect this node to have the following properties: @@ -174,7 +174,7 @@ Feature: Workspace based content publishing | Key | Value | | workspaceName | "user-test" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} When the command SetNodeProperties is executed with payload: | Key | Value | @@ -191,7 +191,7 @@ Feature: Workspace based content publishing | workspaceName | "user-test" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/01-ConstraintChecks.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/01-ConstraintChecks.feature index 90504e16e75..327caf1fa7e 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/01-ConstraintChecks.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/01-ConstraintChecks.feature @@ -30,7 +30,7 @@ Feature: Workspace publication - complex chained functionality | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"language": "de"} + And I am in workspace "live" and dimension space point {"language": "de"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -76,5 +76,5 @@ Feature: Workspace publication - complex chained functionality | workspaceName | "user-ws" | | newContentStreamId | "user-cs-id-yet-again-rebased" | And the graph projection is fully up to date - When I am in the active content stream of workspace "user-ws" and dimension space point {"language": "de"} + When I am in workspace "user-ws" and dimension space point {"language": "de"} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-id-yet-again-rebased;nody-mc-nodeface;{"language": "de"} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/02-BasicFeatures.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/02-BasicFeatures.feature index d7605cc24b9..68984305699 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/02-BasicFeatures.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/02-BasicFeatures.feature @@ -44,7 +44,7 @@ Feature: Individual node publication Scenario: It is possible to publish a single node; and only this one is live. # create nodes in user WS Given I am in workspace "user-test" - And I am in the active content stream of workspace "user-test" + And I am in workspace "user-test" And I am in dimension space point {} And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | nodeTypeName | parentNodeAggregateId | nodeName | tetheredDescendantNodeAggregateIds | @@ -60,7 +60,7 @@ Feature: Individual node publication | contentStreamIdForMatchingPart | "user-cs-identifier-matching" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" Then I expect a node identified by cs-identifier;sir-david-nodenborough;{} to exist in the content graph diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/03-MoreBasicFeatures.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/03-MoreBasicFeatures.feature index cd22380f14c..3b9b33847cd 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/03-MoreBasicFeatures.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/03-MoreBasicFeatures.feature @@ -30,7 +30,7 @@ Feature: Publishing individual nodes (basics) | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -108,7 +108,7 @@ Feature: Publishing individual nodes (basics) | contentStreamIdForMatchingPart | "user-cs-identifier-matching" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | @@ -122,7 +122,7 @@ Feature: Publishing individual nodes (basics) | Key | Value | | image | "Modified image" | - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier-remaining;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | @@ -144,7 +144,7 @@ Feature: Publishing individual nodes (basics) | contentStreamIdForRemainingPart | "user-cs-identifier-remaining" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | @@ -158,7 +158,7 @@ Feature: Publishing individual nodes (basics) | Key | Value | | image | "Initial image" | - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier-remaining;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | @@ -180,7 +180,7 @@ Feature: Publishing individual nodes (basics) | contentStreamIdForRemainingPart | "user-cs-identifier-remaining" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | @@ -194,7 +194,7 @@ Feature: Publishing individual nodes (basics) | Key | Value | | image | "Modified image" | - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier-remaining;sir-david-nodenborough;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/04-AllFeaturePublication.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/04-AllFeaturePublication.feature index dfdb72c5b5f..514ad393268 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/04-AllFeaturePublication.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/04-AllFeaturePublication.feature @@ -39,7 +39,7 @@ Feature: Publishing hide/show scenario of nodes | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -83,7 +83,7 @@ Feature: Publishing hide/show scenario of nodes | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "user-test" + And I am in workspace "user-test" # SETUP: hide two nodes in USER workspace Given the command DisableNodeAggregate is executed with payload: @@ -106,7 +106,7 @@ Feature: Publishing hide/show scenario of nodes | contentStreamIdForMatchingPart | "matching-cs-id" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to no node And I expect node aggregate identifier "nody-mc-nodeface" to lead to no node And I expect node aggregate identifier "sir-nodeward-nodington-iii" to lead to node cs-identifier;sir-nodeward-nodington-iii;{} @@ -160,12 +160,12 @@ Feature: Publishing hide/show scenario of nodes | contentStreamIdForRemainingPart | "user-cs-identifier-modified" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{} And I expect node aggregate identifier "sir-nodeward-nodington-iii" to lead to no node - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier-modified;sir-david-nodenborough;{} And I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-identifier-modified;nody-mc-nodeface;{} And I expect node aggregate identifier "sir-nodeward-nodington-iii" to lead to node user-cs-identifier-modified;sir-nodeward-nodington-iii;{} @@ -200,13 +200,13 @@ Feature: Publishing hide/show scenario of nodes # | nodesToPublish | [{"nodeAggregateId": "sir-david-nodenborough", "contentStreamId": "user-cs-identifier", "dimensionSpacePoint": {}}] | #And the graph projection is fully up to date - # When I am in the active content stream of workspace "live" and dimension space point {} + # When I am in workspace "live" and dimension space point {} ## Then I expect the node aggregate "lady-eleonode-rootford" to have the following child nodes: # | Name | nodeAggregateId | # | text1mod | sir-david-nodenborough | # | image | sir-nodeward-nodington-iii | - # When I am in the active content stream of workspace "user-test" and dimension space point {} + # When I am in workspace "user-test" and dimension space point {} # Then I expect the node aggregate "lady-eleonode-rootford" to have the following child nodes: # | Name | nodeAggregateId | # | text1mod | sir-david-nodenborough | @@ -244,12 +244,12 @@ Feature: Publishing hide/show scenario of nodes | contentStreamIdForRemainingPart | "user-cs-identifier-modified" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to no node Then I expect node aggregate identifier "nody-mc-nodeface" to lead to no node Then I expect node aggregate identifier "sir-nodeward-nodington-iii" to lead to node cs-identifier;sir-nodeward-nodington-iii;{} - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to no node Then I expect node aggregate identifier "nody-mc-nodeface" to lead to no node Then I expect node aggregate identifier "sir-nodeward-nodington-iii" to lead to no node @@ -284,12 +284,12 @@ Feature: Publishing hide/show scenario of nodes | nodesToPublish | [{"nodeAggregateId": "sir-david-nodenborough", "workspaceName": "user-test", "dimensionSpacePoint": {}}] | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to no node Then I expect node aggregate identifier "nody-mc-nodeface" to lead to no node Then I expect node aggregate identifier "sir-nodeward-nodington-iii" to lead to node cs-identifier;sir-nodeward-nodington-iii;{} - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to no node Then I expect node aggregate identifier "nody-mc-nodeface" to lead to no node Then I expect node aggregate identifier "sir-nodeward-nodington-iii" to lead to no node @@ -327,7 +327,7 @@ Feature: Publishing hide/show scenario of nodes | contentStreamIdForRemainingPart | "user-cs-identifier-modified" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to have the following references: | Name | Node | Properties | @@ -340,7 +340,7 @@ Feature: Publishing hide/show scenario of nodes | Name | Node | Properties | | referenceProperty | cs-identifier;sir-david-nodenborough;{} | null | - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier-modified;sir-david-nodenborough;{} And I expect this node to have the following references: | Name | Node | Properties | @@ -389,11 +389,11 @@ Feature: Publishing hide/show scenario of nodes | contentStreamIdForRemainingPart | "user-cs-identifier-modified" | And the graph projection is fully up to date - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "new1-agg" to lead to node cs-identifier;new1-agg;{} Then I expect node aggregate identifier "new2-agg" to lead to no node - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "new1-agg" to lead to node user-cs-identifier-modified;new1-agg;{} Then I expect node aggregate identifier "new2-agg" to lead to node user-cs-identifier-modified;new2-agg;{} diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/05-PublishMovedNodesWithoutDimensions.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/05-PublishMovedNodesWithoutDimensions.feature index 9b2018d0cb1..9da2034d93c 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/05-PublishMovedNodesWithoutDimensions.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/05-PublishMovedNodesWithoutDimensions.feature @@ -23,7 +23,7 @@ Feature: Publishing moved nodes without dimensions | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" + And I am in workspace "live" And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -89,7 +89,7 @@ Feature: Publishing moved nodes without dimensions # node aggregate occupation and coverage is not relevant without dimensions and thus not tested - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-nodeward-nodington-iii" and node path "esquire" to lead to node cs-identifier;sir-nodeward-nodington-iii;{} And I expect this node to be a child of node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no preceding siblings @@ -129,7 +129,7 @@ Feature: Publishing moved nodes without dimensions # node aggregate occupation and coverage is not relevant without dimensions and thus not tested - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "sir-nodeward-nodington-iii" and node path "esquire" to lead to node cs-identifier;sir-nodeward-nodington-iii;{} And I expect this node to be a child of node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no preceding siblings @@ -180,7 +180,7 @@ Feature: Publishing moved nodes without dimensions # node aggregate occupation and coverage is not relevant without dimensions and thus not tested - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And I expect node aggregate identifier "sir-nodeward-nodington-iii" and node path "esquire" to lead to node cs-identifier;sir-nodeward-nodington-iii;{} And I expect this node to be a child of node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no preceding siblings @@ -224,7 +224,7 @@ Feature: Publishing moved nodes without dimensions # node aggregate occupation and coverage is not relevant without dimensions and thus not tested - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} And I expect node aggregate identifier "sir-david-nodenborough" and node path "document" to lead to node cs-identifier;sir-david-nodenborough;{} And I expect this node to be a child of node cs-identifier;lady-eleonode-rootford;{} And I expect this node to have no preceding siblings diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature index 4f19a7d6741..1aac8c9bde1 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/W9-WorkspaceDiscarding/02-DiscardWorkspace.feature @@ -23,7 +23,7 @@ Feature: Workspace discarding - basic functionality | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -58,7 +58,7 @@ Feature: Workspace discarding - basic functionality | propertyValues | {"text": "Modified"} | And the graph projection is fully up to date - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-identifier;nody-mc-nodeface;{} And I expect this node to have the following properties: | Key | Value | @@ -71,7 +71,7 @@ Feature: Workspace discarding - basic functionality | newContentStreamId | "user-cs-identifier-modified" | And the graph projection is fully up to date - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-identifier-modified;nody-mc-nodeface;{} And I expect this node to have the following properties: | Key | Value | @@ -101,7 +101,7 @@ Feature: Workspace discarding - basic functionality | newContentStreamId | "user-cs-identifier-modified" | And the graph projection is fully up to date - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-identifier-modified;nody-mc-nodeface;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/NodeOperationsOnMultipleWorkspaces.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/NodeOperationsOnMultipleWorkspaces.feature index 7e26a903218..7d178996e7a 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/NodeOperationsOnMultipleWorkspaces.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/NodeOperationsOnMultipleWorkspaces.feature @@ -17,7 +17,7 @@ Feature: Single Node operations on multiple workspaces/content streams; e.g. cop | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -73,26 +73,26 @@ Feature: Single Node operations on multiple workspaces/content streams; e.g. cop | propertyValues.text.value | "Changed" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{} And I expect this node to have the following properties: | Key | Value | | text | "Original" | - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-identifier;nody-mc-nodeface;{} And I expect this node to have the following properties: | Key | Value | | text | "Changed" | - When I am in the active content stream of workspace "live" and dimension space point {} + When I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "nodingers-cat" and node path "child/pet" to lead to node cs-identifier;nodingers-cat;{} When I go to the parent node of node aggregate "nodingers-cat" Then I expect this node to have the following properties: | Key | Value | | text | "Original" | - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "nodingers-cat" and node path "child/pet" to lead to node user-cs-identifier;nodingers-cat;{} When I go to the parent node of node aggregate "nodingers-cat" Then I expect this node to have the following properties: diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/PruneContentStreams.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/PruneContentStreams.feature index 92dc9d8e919..34c8e21d299 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/PruneContentStreams.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/PruneContentStreams.feature @@ -14,7 +14,7 @@ Feature: If content streams are not in use anymore by the workspace, they can be | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "root-node" | @@ -47,7 +47,7 @@ Feature: If content streams are not in use anymore by the workspace, they can be | workspaceName | "user-test" | And the graph projection is fully up to date - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then the current content stream has state "IN_USE_BY_WORKSPACE" And the content stream "user-cs-identifier" has state "NO_LONGER_IN_USE" @@ -72,7 +72,7 @@ Feature: If content streams are not in use anymore by the workspace, they can be When I am in content stream "user-cs-identifier" and dimension space point {} Then I expect node aggregate identifier "root-node" to lead to no node - When I am in the active content stream of workspace "user-test" and dimension space point {} + When I am in workspace "user-test" and dimension space point {} Then I expect node aggregate identifier "root-node" to lead to node user-cs-identifier-rebased;root-node;{} Scenario: NO_LONGER_IN_USE content streams can be cleaned up completely (simple case) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/SingleNodeOperationsOnLiveWorkspace.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/SingleNodeOperationsOnLiveWorkspace.feature index 1d68654f937..cee29b67b01 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/SingleNodeOperationsOnLiveWorkspace.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/Workspaces/SingleNodeOperationsOnLiveWorkspace.feature @@ -19,7 +19,7 @@ Feature: Single Node operations on live workspace | workspaceName | "live" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -51,7 +51,7 @@ Feature: Single Node operations on live workspace | propertyValues.text.value | "Hello" | When the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node cs-identifier;nody-mc-nodeface;{} And I expect this node to have the following properties: | Key | Value | diff --git a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php index e0143c235f7..c714869f9b7 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php +++ b/Neos.ContentRepository.TestSuite/Classes/Behavior/Features/Bootstrap/CRTestSuiteRuntimeVariables.php @@ -111,20 +111,6 @@ public function iAmInWorkspace(string $workspaceName): void $this->currentContentStreamId = $workspace->currentContentStreamId; } - /** - * @Given /^I am in the active content stream of workspace "([^"]*)"$/ - * @throws \Exception - */ - public function iAmInTheActiveContentStreamOfWorkspace(string $workspaceName): void - { - $workspace = $this->currentContentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::fromString($workspaceName)); - if ($workspace === null) { - throw new \Exception(sprintf('Workspace "%s" does not exist, projection not yet up to date?', $workspaceName), 1548149355); - } - $this->currentWorkspaceName = WorkspaceName::fromString($workspaceName); - $this->currentContentStreamId = $workspace->currentContentStreamId; - } - /** * @Given /^I am in dimension space point (.*)$/ */ @@ -152,16 +138,6 @@ public function iAmInContentStreamAndDimensionSpacePoint(string $contentStreamId $this->iAmInDimensionSpacePoint($dimensionSpacePoint); } - /** - * @Given /^I am in the active content stream of workspace "([^"]*)" and dimension space point (.*)$/ - * @throws \Exception - */ - public function iAmInTheActiveContentStreamOfWorkspaceAndDimensionSpacePoint(string $workspaceName, string $dimensionSpacePoint): void - { - $this->iAmInTheActiveContentStreamOfWorkspace($workspaceName); - $this->iAmInDimensionSpacePoint($dimensionSpacePoint); - } - /** * @When /^VisibilityConstraints are set to "(withoutRestrictions|frontend)"$/ */ diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature index 9ede1e5a1e8..2d4a8a34fd7 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/Assets.feature @@ -53,7 +53,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on asset changes | workspaceName | "user-test" | | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "root" | @@ -260,7 +260,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on asset changes Scenario: ContentCache gets flushed for user workspace when a referenced asset in a property text has changed Given I have Fusion content cache enabled - And I am in the active content stream of workspace "user-test" and dimension space point {} + And I am in workspace "user-test" and dimension space point {} And the Fusion context node is a2 And I execute the following Fusion code: @@ -274,11 +274,11 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on asset changes cacheVerifier=first execution, text=Link to asset://an-asset-to-change. """ - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} Then the asset "an-asset-to-change" has the title "First changed asset" And the ContentCacheFlusher flushes all collected tags - And I am in the active content stream of workspace "user-test" and dimension space point {} + And I am in workspace "user-test" and dimension space point {} Then the Fusion context node is a2 And I execute the following Fusion code: """fusion diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature index 15142b1045a..fad01fcfe6c 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/ConvertUris.feature @@ -36,7 +36,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag | workspaceName | "user-test" | | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "root" | @@ -135,7 +135,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag Scenario: ContentCache doesn't get flushed when target node changes in different workspace Given I have Fusion content cache enabled - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the Fusion context node is a2 And I execute the following Fusion code: @@ -149,7 +149,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag cacheVerifier=first execution, title=Node a2, link=Some value with node URI: /a1. """ - And I am in the active content stream of workspace "user-test" and dimension space point {} + And I am in workspace "user-test" and dimension space point {} When the command SetNodeProperties is executed with payload: | Key | Value | | contentStreamId | "cs-identifier" | @@ -158,7 +158,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on DynamicNodeTag And the graph projection is fully up to date And The documenturipath projection is up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the Fusion context node is a2 And I execute the following Fusion code: """fusion diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature index c058092286a..840166ccdf4 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/Nodes.feature @@ -33,7 +33,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "root" | diff --git a/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInOtherWorkspace.feature b/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInOtherWorkspace.feature index 457b9d716b3..183dc20ffcc 100644 --- a/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInOtherWorkspace.feature +++ b/Neos.Neos/Tests/Behavior/Features/ContentCache/NodesInOtherWorkspace.feature @@ -38,7 +38,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety | workspaceName | "user-test" | | baseWorkspaceName | "live" | | newContentStreamId | "user-cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "root" | @@ -121,7 +121,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety Scenario: ContentCache doesn't get flushed when a property of a node in other workspace has changed Given I have Fusion content cache enabled - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the Fusion context node is a1 And I execute the following Fusion code: @@ -135,7 +135,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety cacheVerifier=first execution, title=Node a1 """ - And I am in the active content stream of workspace "user-test" and dimension space point {} + And I am in workspace "user-test" and dimension space point {} When the command SetNodeProperties is executed with payload: | Key | Value | | contentStreamId | "cs-identifier" | @@ -143,7 +143,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety | propertyValues | {"title": "Node a1 new"} | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the Fusion context node is a1 And I execute the following Fusion code: """fusion @@ -158,7 +158,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety Scenario: ContentCache gets not flushed when a property of another node has changed in different workspace Given I have Fusion content cache enabled - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the Fusion context node is a1 And I execute the following Fusion code: @@ -171,7 +171,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety """ cacheVerifier=first execution, title=Node a1 """ - And I am in the active content stream of workspace "user-test" and dimension space point {} + And I am in workspace "user-test" and dimension space point {} When the command SetNodeProperties is executed with payload: | Key | Value | | contentStreamId | "cs-identifier" | @@ -179,7 +179,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety | propertyValues | {"title": "Node a2 new"} | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the Fusion context node is a1 And I execute the following Fusion code: """fusion @@ -194,7 +194,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety Scenario: ContentCache doesn't get flushed when a property of a node has changed by NodeType name in different workspace Given I have Fusion content cache enabled - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the Fusion context node is a2 And I execute the following Fusion code: """fusion @@ -207,7 +207,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety cacheVerifier=first execution, title=Node a2 """ - And I am in the active content stream of workspace "user-test" and dimension space point {} + And I am in workspace "user-test" and dimension space point {} When the command SetNodeProperties is executed with payload: | Key | Value | | contentStreamId | "cs-identifier" | @@ -215,7 +215,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety | propertyValues | {"title": "Node a1 new"} | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the Fusion context node is a2 And I execute the following Fusion code: """fusion @@ -230,7 +230,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety Scenario: ContentCache doesn't get flushed when a property of a node has changed of a descendant node in different workspace Given I have Fusion content cache enabled - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the Fusion context node is "a1" And I execute the following Fusion code: """fusion @@ -243,7 +243,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety cacheVerifier=first execution, title=Node a1 """ - And I am in the active content stream of workspace "user-test" and dimension space point {} + And I am in workspace "user-test" and dimension space point {} When the command SetNodeProperties is executed with payload: | Key | Value | | contentStreamId | "cs-identifier" | @@ -251,7 +251,7 @@ Feature: Tests for the ContentCacheFlusher and cache flushing on node and nodety | propertyValues | {"title": "Node a1-1 new"} | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the Fusion context node is "a1" And I execute the following Fusion code: """fusion diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Basic.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Basic.feature index b8034963389..7c360e6a7f9 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Basic.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Basic.feature @@ -31,7 +31,7 @@ Feature: Basic routing functionality (match & resolve document nodes in one dime | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -45,7 +45,7 @@ Feature: Basic routing functionality (match & resolve document nodes in one dime # earl-o-documentbourgh # nody-mc-nodeface # - And I am in content stream "cs-identifier" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | | shernode-homes | lady-eleonode-rootford | Neos.Neos:Test.Routing.Page | {"uriPathSegment": "ignore-me"} | node1 | diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Dimensions.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Dimensions.feature index ea4f8bc969b..6e40d5409db 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Dimensions.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Dimensions.feature @@ -43,7 +43,7 @@ Feature: Routing functionality with multiple content dimensions | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {"market":"DE", "language":"en"} + And I am in workspace "live" and dimension space point {"market":"DE", "language":"en"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/DisableNodes.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/DisableNodes.feature index 5715f273bb2..7539f67e1af 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/DisableNodes.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/DisableNodes.feature @@ -30,7 +30,7 @@ Feature: Routing behavior of removed, disabled and re-enabled nodes | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Lowlevel_ProjectionTests.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Lowlevel_ProjectionTests.feature index 2ca042c1632..54999dbed75 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Lowlevel_ProjectionTests.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Lowlevel_ProjectionTests.feature @@ -39,7 +39,7 @@ Feature: Low level tests covering the inner behavior of the routing projection | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -236,7 +236,7 @@ Feature: Low level tests covering the inner behavior of the routing projection | "c" | "lady-eleonode-rootford/shernode-homes/c" | "c" | "shernode-homes" | "b" | null | "Neos.Neos:Test.Routing.Page" | Scenario: ab(> b1, b2 > b2a)c => a(> b2 > b2a)b(> b1)c (moving b1 below a) - And I am in content stream "cs-identifier" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | | b1 | b | Neos.Neos:Test.Routing.Page | {"uriPathSegment": "b1"} | b1 | @@ -261,7 +261,7 @@ Feature: Low level tests covering the inner behavior of the routing projection | "c" | "lady-eleonode-rootford/shernode-homes/c" | "c" | "shernode-homes" | "b" | null | "Neos.Neos:Test.Routing.Page" | Scenario: ab(> b1, b2 > b2a)c => b(> b1, a, b2 > b2a)c (moving a below b) - And I am in content stream "cs-identifier" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | | b1 | b | Neos.Neos:Test.Routing.Page | {"uriPathSegment": "b1"} | b1 | diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MultiSiteLinking.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MultiSiteLinking.feature index a86d7b7f861..3d402e4ee54 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MultiSiteLinking.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/MultiSiteLinking.feature @@ -39,7 +39,7 @@ Feature: Linking between multiple websites | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeCreationEdgeCases.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeCreationEdgeCases.feature index b69372be4c0..fe34203f36f 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeCreationEdgeCases.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeCreationEdgeCases.feature @@ -28,7 +28,7 @@ Feature: Test cases for node creation edge cases | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And I am in workspace "live" and dimension space point {"example":"source"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeVariationEdgeCases.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeVariationEdgeCases.feature index fb562284490..8265f425db0 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeVariationEdgeCases.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/NodeVariationEdgeCases.feature @@ -28,7 +28,7 @@ Feature: Test cases for node variation edge cases | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And I am in workspace "live" and dimension space point {"example":"source"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -137,7 +137,7 @@ Feature: Test cases for node variation edge cases | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And I am in workspace "live" and dimension space point {"example":"source"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -235,7 +235,7 @@ Feature: Test cases for node variation edge cases | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And I am in workspace "live" and dimension space point {"example":"source"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/RouteCache.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/RouteCache.feature index ba1cee6fff5..6a39474e497 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/RouteCache.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/RouteCache.feature @@ -30,7 +30,7 @@ Feature: Route cache invalidation | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -44,7 +44,7 @@ Feature: Route cache invalidation # earl-o-documentbourgh # nody-mc-nodeface # - And I am in content stream "cs-identifier" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | | shernode-homes | lady-eleonode-rootford | Neos.Neos:Test.Routing.Page | {"uriPathSegment": "ignore-me"} | node1 | diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Shortcuts.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Shortcuts.feature index 5a2f920db40..d937e5607a7 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Shortcuts.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/Shortcuts.feature @@ -47,7 +47,7 @@ Feature: Routing behavior of shortcut nodes | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | @@ -244,7 +244,7 @@ Feature: Routing behavior of shortcut nodes Then the node "shortcut-first-child-node" in content stream "cs-identifier" and dimension "{}" should resolve to URL "http://www.neos.io/" Scenario: Change shortcut targetMode from "parentNode" to "firstChildNode" - And I am in content stream "cs-identifier" and dimension space point {} + And I am in workspace "live" and dimension space point {} When the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | | new-child-node | shortcut-parent-node | Neos.Neos:Test.Routing.Page | {"uriPathSegment": "new-child"} | new | @@ -333,7 +333,7 @@ Feature: Routing behavior of shortcut nodes Then The node "invalid-shortcut-selected-node" in content stream "cs-identifier" and dimension "{}" should not resolve to an URL Scenario: Recursive shortcuts - And I am in content stream "cs-identifier" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | | level-1 | shortcuts | Neos.Neos:Shortcut | {"uriPathSegment": "level1", "targetMode": "selectedTarget", "target": "node://level-2"} | level1 | @@ -344,7 +344,7 @@ Feature: Routing behavior of shortcut nodes Then the node "level-2" in content stream "cs-identifier" and dimension "{}" should resolve to URL "/david-nodenborough/shortcuts/shortcut-first-child/first-child-node" Scenario: Unlimited recursive shortcuts - And I am in content stream "cs-identifier" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the following CreateNodeAggregateWithNode commands are executed: | nodeAggregateId | parentNodeAggregateId | nodeTypeName | initialPropertyValues | nodeName | | node-a | shortcuts | Neos.Neos:Shortcut | {"uriPathSegment": "a", "targetMode": "selectedTarget", "target": "node://node-b"} | node-a | diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/TetheredSiteChildDocuments.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/TetheredSiteChildDocuments.feature index 59d78d15a6d..d3ae4a3ee7d 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/TetheredSiteChildDocuments.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/TetheredSiteChildDocuments.feature @@ -30,7 +30,7 @@ Feature: Tests for site node child documents. These are special in that they hav | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/UnknownNodeTypes.feature b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/UnknownNodeTypes.feature index 7923dac5f9e..fee65d02083 100644 --- a/Neos.Neos/Tests/Behavior/Features/FrontendRouting/UnknownNodeTypes.feature +++ b/Neos.Neos/Tests/Behavior/Features/FrontendRouting/UnknownNodeTypes.feature @@ -24,7 +24,7 @@ Feature: Basic routing functionality (match & resolve nodes with unknown types) | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCase.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCase.feature index b40c3da18f8..d3ed34d23d2 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCase.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCase.feature @@ -33,7 +33,7 @@ Feature: Tests for the "Neos.Neos:ContentCase" Fusion prototype | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "root" | diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCollection.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCollection.feature index a75d67a0c3d..40dbdcedf35 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCollection.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/ContentCollection.feature @@ -41,7 +41,7 @@ Feature: Tests for the "Neos.Neos:ContentCollection" Fusion prototype | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "root" | diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/ConvertUris.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/ConvertUris.feature index db389b0ad3c..da76e6f9217 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/ConvertUris.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/ConvertUris.feature @@ -30,7 +30,7 @@ Feature: Tests for the "Neos.Neos:ConvertUris" Fusion prototype | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "root" | diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature index 54870e7901b..26fb978b2d4 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature @@ -46,7 +46,7 @@ Feature: Tests for the "Neos.Neos:Menu" and related Fusion prototypes | Key | Value | | workspaceName | "live" | | newContentStreamId | "cs-identifier" | - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "root" | diff --git a/Neos.TimeableNodeVisibility/Tests/Behavior/Features/HandleExceeded.feature b/Neos.TimeableNodeVisibility/Tests/Behavior/Features/HandleExceeded.feature index 845bb8fc66c..02bc27ece9b 100644 --- a/Neos.TimeableNodeVisibility/Tests/Behavior/Features/HandleExceeded.feature +++ b/Neos.TimeableNodeVisibility/Tests/Behavior/Features/HandleExceeded.feature @@ -33,7 +33,7 @@ Feature: Simple handling of nodes with exceeded enableAfter and disableAfter dat | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date And I am in workspace "live" - And I am in the active content stream of workspace "live" and dimension space point {} + And I am in workspace "live" and dimension space point {} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | | nodeAggregateId | "lady-eleonode-rootford" | From 22c824999460e2438a1181e86f388a13e30116da Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 10 May 2024 19:17:20 +0200 Subject: [PATCH 160/214] TASK: Remove obsolete `getSubgraphs` method --- .../src/Domain/Repository/ContentGraph.php | 10 ---------- .../src/Domain/Projection/HypergraphProjection.php | 5 ----- 2 files changed, 15 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 9273b36a956..a11b50f9b29 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -20,7 +20,6 @@ use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; use Neos\ContentGraph\DoctrineDbalAdapter\ContentGraphTableNames; -use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjection; use Neos\ContentGraph\DoctrineDbalAdapter\NodeQueryBuilder; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; @@ -316,15 +315,6 @@ public function findUsedNodeTypeNames(): iterable ); } - /** - * @return ContentSubgraphWithRuntimeCaches[] - * @internal only used for {@see DoctrineDbalContentGraphProjection} - */ - public function getSubgraphs(): array - { - return $this->subgraphs; - } - private function createQueryBuilder(): QueryBuilder { return $this->client->getConnection()->createQueryBuilder(); diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php index c8a544e935a..794b625b1cc 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Projection/HypergraphProjection.php @@ -149,11 +149,6 @@ public function reset(): void $this->checkpointStorage->acquireLock(); $this->checkpointStorage->updateAndReleaseLock(SequenceNumber::none()); - - //$contentGraph = $this->getState(); - //foreach ($contentGraph->getSubgraphs() as $subgraph) { - // $subgraph->inMemoryCache->enable(); - //} } private function truncateDatabaseTables(): void From aa52b35dce6abc33c6aa50f0f422129f3e98edc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCller?= Date: Fri, 10 May 2024 21:01:37 +0200 Subject: [PATCH 161/214] Apply suggestions from code review Co-authored-by: Marc Henry Schultz <85400359+mhsdesign@users.noreply.github.com> --- .../Classes/Feature/Common/ConstraintChecks.php | 4 ++-- .../Classes/Feature/ContentStreamCommandHandler.php | 1 - Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 5af6ec79133..01635643acb 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -671,7 +671,7 @@ protected function requireNodeAggregateToOccupyDimensionSpacePoint( ): void { if (!$nodeAggregate->occupiesDimensionSpacePoint($originDimensionSpacePoint)) { throw new DimensionSpacePointIsNotYetOccupied( - 'Dimension space point ' . json_encode($originDimensionSpacePoint, JSON_THROW_ON_ERROR) + 'Dimension space point ' . json_encode($originDimensionSpacePoint, JSON_PARTIAL_OUTPUT_ON_ERROR) . ' is not yet occupied by node aggregate "' . $nodeAggregate->nodeAggregateId->value . '"', 1552595396 ); @@ -687,7 +687,7 @@ protected function requireNodeAggregateToNotOccupyDimensionSpacePoint( ): void { if ($nodeAggregate->occupiesDimensionSpacePoint($originDimensionSpacePoint)) { throw new DimensionSpacePointIsAlreadyOccupied( - 'Dimension space point ' . json_encode($originDimensionSpacePoint, JSON_THROW_ON_ERROR) + 'Dimension space point ' . json_encode($originDimensionSpacePoint, JSON_PARTIAL_OUTPUT_ON_ERROR) . ' is already occupied by node aggregate "' . $nodeAggregate->nodeAggregateId->value . '"', 1552595441 ); diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php index 882a700f313..cbaaba4596c 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php @@ -134,7 +134,6 @@ private function handleForkContentStream( $this->requireContentStreamToExist($command->sourceContentStreamId, $commandHandlingDependencies); $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId, $commandHandlingDependencies); - // TODO: This is not great $sourceContentStreamVersion = $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($command->sourceContentStreamId); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->newContentStreamId) diff --git a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php index 50dcf7a87de..0924a557057 100644 --- a/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php +++ b/Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php @@ -65,8 +65,6 @@ public function __construct( /** * Main entry point to *directly* flush the caches of a given NodeAggregate * - * FIXME workspaceName instead of contentStreamId - * * @param ContentRepository $contentRepository * @param ContentStreamId $contentStreamId * @param NodeAggregateId $nodeAggregateId From c0b5579f040406026132b4f78514b4cf0233c907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCller?= Date: Fri, 10 May 2024 21:03:07 +0200 Subject: [PATCH 162/214] Remove unnecessary empty line Co-authored-by: Marc Henry Schultz <85400359+mhsdesign@users.noreply.github.com> --- Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php b/Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php index cfb347b0a6d..d373d57f9cc 100644 --- a/Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php +++ b/Neos.Neos/Classes/AssetUsage/Dto/AssetIdAndOriginalAssetId.php @@ -9,7 +9,6 @@ /** * @internal */ - #[Flow\Proxy(false)] readonly class AssetIdAndOriginalAssetId { From 8d9de7a02669ac0f7531d55e494533556a7b1edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 10 May 2024 21:08:57 +0200 Subject: [PATCH 163/214] Re-add content stream does not exist check --- .../Feature/ContentStreamCommandHandler.php | 25 +++++++++++++++++-- .../ContentStream/ContentStreamFinder.php | 17 +++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php index cbaaba4596c..2c9d9160ccd 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/ContentStreamCommandHandler.php @@ -53,7 +53,7 @@ public function canHandle(CommandInterface $command): bool public function handle(CommandInterface $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { return match ($command::class) { - CreateContentStream::class => $this->handleCreateContentStream($command), + CreateContentStream::class => $this->handleCreateContentStream($command, $commandHandlingDependencies), CloseContentStream::class => $this->handleCloseContentStream($command, $commandHandlingDependencies), ReopenContentStream::class => $this->handleReopenContentStream($command, $commandHandlingDependencies), ForkContentStream::class => $this->handleForkContentStream($command, $commandHandlingDependencies), @@ -66,8 +66,10 @@ public function handle(CommandInterface $command, CommandHandlingDependencies $c * @throws ContentStreamAlreadyExists */ private function handleCreateContentStream( - CreateContentStream $command + CreateContentStream $command, + CommandHandlingDependencies $commandHandlingDependencies, ): EventsToPublish { + $this->requireContentStreamToNotExistYet($command->contentStreamId, $commandHandlingDependencies); $streamName = ContentStreamEventStreamName::fromContentStreamId($command->contentStreamId) ->getEventStreamName(); @@ -133,6 +135,7 @@ private function handleForkContentStream( ): EventsToPublish { $this->requireContentStreamToExist($command->sourceContentStreamId, $commandHandlingDependencies); $this->requireContentStreamToNotBeClosed($command->sourceContentStreamId, $commandHandlingDependencies); + $this->requireContentStreamToNotExistYet($command->newContentStreamId, $commandHandlingDependencies); $sourceContentStreamVersion = $commandHandlingDependencies->getContentStreamFinder()->findVersionForContentStream($command->sourceContentStreamId); @@ -177,6 +180,24 @@ private function handleRemoveContentStream( /** * @param ContentStreamId $contentStreamId + * @param CommandHandlingDependencies $commandHandlingDependencies + * @throws ContentStreamAlreadyExists + */ + protected function requireContentStreamToNotExistYet( + ContentStreamId $contentStreamId, + CommandHandlingDependencies $commandHandlingDependencies + ): void { + if ($commandHandlingDependencies->getContentStreamFinder()->hasContentStream($contentStreamId)) { + throw new ContentStreamAlreadyExists( + 'Content stream "' . $contentStreamId->value . '" already exists.', + 1521386345 + ); + } + } + + /** + * @param ContentStreamId $contentStreamId + * @param CommandHandlingDependencies $commandHandlingDependencies * @throws ContentStreamDoesNotExistYet */ protected function requireContentStreamToExist( diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php b/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php index c159398fd06..6bcc6a9b2b0 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentStream/ContentStreamFinder.php @@ -179,4 +179,21 @@ public function findVersionForContentStream(ContentStreamId $contentStreamId): M return MaybeVersion::fromVersionOrNull(Version::fromInteger($version)); } + + public function hasContentStream(ContentStreamId $contentStreamId): bool + { + $connection = $this->client->getConnection(); + /* @var $state string|false */ + $version = $connection->executeQuery( + ' + SELECT version FROM ' . $this->tableName . ' + WHERE contentStreamId = :contentStreamId + ', + [ + 'contentStreamId' => $contentStreamId->value + ] + )->fetchOne(); + + return $version !== false; + } } From 0078b7b5373d5666ff0fe322d4632e0d4edfaacc Mon Sep 17 00:00:00 2001 From: Bastian Waidelich Date: Sat, 11 May 2024 11:23:29 +0200 Subject: [PATCH 164/214] Don't expose ContentGraphTableNames tableNamePrefix --- ...ectionIntegrityViolationDetectionTrait.php | 22 +++++++++---------- .../src/ContentGraphTableNames.php | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php index 26f4a6651f1..28aad3f9561 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php @@ -52,11 +52,11 @@ trait ProjectionIntegrityViolationDetectionTrait */ abstract private function getObject(string $className): object; - protected function getTableNamePrefix(): string + private function tableNames(): ContentGraphTableNames { return ContentGraphTableNames::create( $this->currentContentRepository->id - )->tableNamePrefix; + ); } public function setupDbalGraphAdapterIntegrityViolationTrait() @@ -80,7 +80,7 @@ public function iRemoveTheFollowingSubtreeTag(TableNode $payloadTable): void throw new \RuntimeException(sprintf('Failed to remove subtree tag "%s" because that tag is not set', $subtreeTagToRemove->value), 1708618267); } $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_hierarchyrelation', + $this->tableNames()->hierarchyRelation(), [ 'subtreetags' => json_encode($subtreeTags->without($subtreeTagToRemove), JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT), ], @@ -97,7 +97,7 @@ public function iAddTheFollowingHierarchyRelation(TableNode $payloadTable): void $dataset = $this->transformPayloadTableToDataset($payloadTable); $record = $this->transformDatasetToHierarchyRelationRecord($dataset); $this->dbalClient->getConnection()->insert( - $this->getTableNamePrefix() . '_hierarchyrelation', + $this->tableNames()->hierarchyRelation(), $record ); } @@ -114,7 +114,7 @@ public function iChangeTheFollowingHierarchyRelationsDimensionSpacePointHash(Tab unset($record['position']); $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_hierarchyrelation', + $this->tableNames()->hierarchyRelation(), [ 'dimensionspacepointhash' => $dataset['newDimensionSpacePointHash'] ], @@ -134,7 +134,7 @@ public function iChangeTheFollowingHierarchyRelationsEdgeName(TableNode $payload unset($record['position']); $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_hierarchyrelation', + $this->tableNames()->hierarchyRelation(), [ 'name' => $dataset['newName'] ], @@ -158,7 +158,7 @@ public function iSetTheFollowingPosition(TableNode $payloadTable): void ]; $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_hierarchyrelation', + $this->tableNames()->hierarchyRelation(), [ 'position' => $dataset['newPosition'] ], @@ -176,7 +176,7 @@ public function iDetachTheFollowingReferenceRelationFromItsSource(TableNode $pay $dataset = $this->transformPayloadTableToDataset($payloadTable); $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_referencerelation', + $this->tableNames()->referenceRelation(), [ 'nodeanchorpoint' => 7777777 ], @@ -194,7 +194,7 @@ public function iSetTheFollowingReferencePosition(TableNode $payloadTable): void $dataset = $this->transformPayloadTableToDataset($payloadTable); $this->dbalClient->getConnection()->update( - $this->getTableNamePrefix() . '_referencerelation', + $this->tableNames()->referenceRelation(), [ 'position' => $dataset['newPosition'] ], @@ -265,8 +265,8 @@ private function findHierarchyRelationByIds( ): array { $nodeRecord = $this->dbalClient->getConnection()->executeQuery( 'SELECT h.* - FROM ' . $this->getTableNamePrefix() . '_node n - INNER JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation h + FROM ' . $this->tableNames()->node() . ' n + INNER JOIN ' . $this->tableNames()->hierarchyRelation() . ' h ON n.relationanchorpoint = h.childnodeanchor WHERE n.nodeaggregateid = :nodeAggregateId AND h.contentstreamid = :contentStreamId diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php index 438a4ec0e19..0804d77e7e0 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/ContentGraphTableNames.php @@ -11,7 +11,7 @@ final readonly class ContentGraphTableNames { private function __construct( - public string $tableNamePrefix + private string $tableNamePrefix ) { } From 6a47318dff8a6bcb3082a16fae6e674e903f8a50 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Sat, 11 May 2024 09:28:56 +0000 Subject: [PATCH 165/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index 191035eaea2..5e81e6834b0 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-05-09 +The following reference was automatically generated from code on 2024-05-11 .. _`Neos Command Reference: NEOS.FLOW`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index 53f872ce65f..444e3873b39 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-05-09 +This reference was automatically generated from code on 2024-05-11 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index 913cb604ac0..b1477b8a178 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-05-09 +This reference was automatically generated from code on 2024-05-11 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index dd645be4d8a..64f0051c183 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-05-09 +This reference was automatically generated from code on 2024-05-11 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index 194f066df7a..50b53880cbb 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-05-09 +This reference was automatically generated from code on 2024-05-11 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index 8112dce9e69..6e4e1f89bee 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-05-09 +This reference was automatically generated from code on 2024-05-11 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From 01c3817c5a471c83796abd6ec570021e0eb064c9 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 11 May 2024 13:25:12 +0200 Subject: [PATCH 166/214] 4150 - Adjust NodeQueryBuilder to node names --- .../src/NodeQueryBuilder.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php index 9fd82b24be9..1997347d513 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/NodeQueryBuilder.php @@ -42,7 +42,7 @@ public function __construct( public function buildBasicNodeAggregateQuery(): QueryBuilder { $queryBuilder = $this->createQueryBuilder() - ->select('n.*, h.contentstreamid, h.name, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') + ->select('n.*, h.contentstreamid, h.subtreetags, dsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNames->node(), 'n') ->innerJoin('n', $this->tableNames->hierarchyRelation(), 'h', 'h.childnodeanchor = n.relationanchorpoint') ->innerJoin('h', $this->tableNames->dimensionSpacePoints(), 'dsp', 'dsp.hash = h.dimensionspacepointhash') @@ -54,7 +54,7 @@ public function buildBasicNodeAggregateQuery(): QueryBuilder public function buildChildNodeAggregateQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId): QueryBuilder { return $this->createQueryBuilder() - ->select('cn.*, ch.name, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') + ->select('cn.*, ch.contentstreamid, ch.subtreetags, cdsp.dimensionspacepoint AS covereddimensionspacepoint') ->from($this->tableNames->node(), 'pn') ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ph', 'ph.childnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ch', 'ch.parentnodeanchor = pn.relationanchorpoint') @@ -86,7 +86,7 @@ public function buildFindRootNodeAggregatesQuery(ContentStreamId $contentStreamI return $queryBuilder; } - public function buildBasicNodeQuery(ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, string $nodeTableAlias = 'n', string $select = 'n.*, h.name, h.subtreetags'): QueryBuilder + public function buildBasicNodeQuery(ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, string $nodeTableAlias = 'n', string $select = 'n.*, h.subtreetags'): QueryBuilder { return $this->createQueryBuilder() ->select($select) @@ -99,7 +99,7 @@ public function buildBasicNodeQuery(ContentStreamId $contentStreamId, DimensionS public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { return $this->createQueryBuilder() - ->select('n.*, h.name, h.subtreetags') + ->select('n.*, h.subtreetags') ->from($this->tableNames->node(), 'pn') ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'h', 'h.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->node(), 'n', 'h.childnodeanchor = n.relationanchorpoint') @@ -111,7 +111,7 @@ public function buildBasicChildNodesQuery(NodeAggregateId $parentNodeAggregateId public function buildBasicParentNodeQuery(NodeAggregateId $childNodeAggregateId, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint): QueryBuilder { return $this->createQueryBuilder() - ->select('pn.*, ch.name, ch.subtreetags') + ->select('pn.*, ch.subtreetags') ->from($this->tableNames->node(), 'pn') ->innerJoin('pn', $this->tableNames->hierarchyRelation(), 'ph', 'ph.parentnodeanchor = pn.relationanchorpoint') ->innerJoin('pn', $this->tableNames->node(), 'cn', 'cn.relationanchorpoint = ph.childnodeanchor') From f1a0ac527b3eaadc488f89b5028a67257638e9b4 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 11 May 2024 13:26:18 +0200 Subject: [PATCH 167/214] 4150 - Adjust findChildNodeAggregateByName to interface (and reality) --- .../src/Domain/Repository/ContentGraph.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 909667d052a..d21056908c7 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -253,7 +253,7 @@ public function findTetheredChildNodeAggregates(NodeAggregateId $parentNodeAggre public function findChildNodeAggregateByName( NodeAggregateId $parentNodeAggregateId, NodeName $name - ): NodeAggregate { + ): ?NodeAggregate { $queryBuilder = $this->nodeQueryBuilder->buildChildNodeAggregateQuery($parentNodeAggregateId, $this->contentStreamId) ->andWhere('cn.name = :relationName') ->setParameter('relationName', $name->value); @@ -321,7 +321,7 @@ private function createQueryBuilder(): QueryBuilder return $this->client->getConnection()->createQueryBuilder(); } - private function mapQueryBuilderToNodeAggregate(QueryBuilder $queryBuilder): NodeAggregate + private function mapQueryBuilderToNodeAggregate(QueryBuilder $queryBuilder): ?NodeAggregate { return $this->nodeFactory->mapNodeRowsToNodeAggregate( $this->fetchRows($queryBuilder), From e58c81a5498f97c06311e1718fded91d2b9ae3c2 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 11 May 2024 13:26:33 +0200 Subject: [PATCH 168/214] Prevent renaming nodes on a closed content stream --- .../Classes/Feature/NodeRenaming/NodeRenaming.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php index c275045c19a..547a0693554 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeRenaming/NodeRenaming.php @@ -33,6 +33,7 @@ trait NodeRenaming private function handleChangeNodeAggregateName(ChangeNodeAggregateName $command, CommandHandlingDependencies $commandHandlingDependencies): EventsToPublish { $contentGraph = $commandHandlingDependencies->getContentGraph($command->workspaceName); + $this->requireContentStream($command->workspaceName, $commandHandlingDependencies); $expectedVersion = $this->getExpectedVersionOfContentStream($contentGraph->getContentStreamId(), $commandHandlingDependencies); $nodeAggregate = $this->requireProjectedNodeAggregate( $contentGraph, From ae737d011df95a620ad30bb055df945501b94b26 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 11 May 2024 13:27:00 +0200 Subject: [PATCH 169/214] 4150 - Adjust site handling --- .../Controller/Module/Administration/SitesController.php | 2 +- Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php index ebcf45b0831..06828e270da 100755 --- a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php +++ b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php @@ -204,7 +204,7 @@ public function updateSiteAction(Site $site, $newSiteNodeName) } foreach ($contentRepository->getWorkspaceFinder()->findAll() as $workspace) { - $siteNodeAggregate = $contentRepository->getContentGraph()->findChildNodeAggregateByName( + $siteNodeAggregate = $contentRepository->getContentGraph(WorkspaceName::forLive())->findChildNodeAggregateByName( $sitesNode->nodeAggregateId, $site->getNodeName()->toNodeName() ); diff --git a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php index f61eb25a1a1..062e7ea8f18 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php +++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php @@ -30,6 +30,7 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Neos\Domain\Exception\SiteNodeTypeIsInvalid; use Neos\Neos\Domain\Model\Site; use Neos\Neos\Domain\Model\SiteNodeName; @@ -95,7 +96,7 @@ public function createSiteNodeIfNotExists(Site $site, string $nodeTypeName): voi throw SiteNodeTypeIsInvalid::becauseItIsNotOfTypeSite(NodeTypeName::fromString($nodeTypeName)); } - $siteNodeAggregate = $this->contentRepository->getContentGraph()->findChildNodeAggregateByName( + $siteNodeAggregate = $this->contentRepository->getContentGraph(WorkspaceName::forLive())->findChildNodeAggregateByName( $sitesNodeIdentifier, $site->getNodeName()->toNodeName(), ); From 725aae9313b7261090c922c7f4d7769d7bfadba0 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 11 May 2024 13:28:53 +0200 Subject: [PATCH 170/214] 4150 - Adjust node name comments --- .../src/Domain/Projection/NodeRecord.php | 1 - .../Classes/Projection/ContentGraph/Node.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php index 6ebe8c10c97..5af86607ea4 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Projection/NodeRecord.php @@ -41,7 +41,6 @@ public function __construct( public SerializedPropertyValues $properties, public NodeTypeName $nodeTypeName, public NodeAggregateClassification $classification, - /** Transient node name to store a node name after fetching a node with hierarchy (not always available) */ public ?NodeName $nodeName, public Timestamps $timestamps, ) { diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index e89b8aec790..48250a8107c 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -46,7 +46,7 @@ * @param NodeTypeName $nodeTypeName The node's node type name; always set, even if unknown to the NodeTypeManager * @param NodeType|null $nodeType The node's node type, null if unknown to the NodeTypeManager - @deprecated Don't rely on this too much, as the capabilities of the NodeType here will probably change a lot; Ask the {@see NodeTypeManager} instead * @param PropertyCollection $properties All properties of this node. References are NOT part of this API; To access references, {@see ContentSubgraphInterface::findReferences()} can be used; To read the serialized properties use {@see PropertyCollection::serialized()}. - * @param NodeName|null $nodeName The optionally named hierarchy relation to the node's parent. + * @param NodeName|null $nodeName The optional name of the node, describing its relation to its parent * @param NodeTags $tags explicit and inherited SubtreeTags of this node * @param Timestamps $timestamps Creation and modification timestamps of this node */ From 07f4afde6f4b05c3ff53c389bfdf74b6d84ffc69 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 11 May 2024 13:44:54 +0200 Subject: [PATCH 171/214] 4150 - Adjust Integrity violation trait to TableNames --- .../Bootstrap/ProjectionIntegrityViolationDetectionTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php index 8f987ef9bc2..873a700ea17 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/Tests/Behavior/Features/Bootstrap/ProjectionIntegrityViolationDetectionTrait.php @@ -133,8 +133,8 @@ public function iChangeTheFollowingNodesName(TableNode $payloadTable): void $dataset = $this->transformPayloadTableToDataset($payloadTable); $relationAnchorPoint = $this->dbalClient->getConnection()->executeQuery( - 'SELECT n.relationanchorpoint FROM ' . $this->getTableNamePrefix() . '_node n - JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation h ON h.childnodeanchor = n.relationanchorpoint + 'SELECT n.relationanchorpoint FROM ' . $this->tableNames()->node() . ' n + JOIN ' . $this->tableNames()->hierarchyRelation() . ' h ON h.childnodeanchor = n.relationanchorpoint WHERE h.contentstreamid = :contentStreamId AND n.nodeaggregateId = :nodeAggregateId AND n.origindimensionspacepointhash = :originDimensionSpacePointHash', From 07f031f92f4a892cab9684637302ff2a46759f92 Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 11 May 2024 15:02:54 +0200 Subject: [PATCH 172/214] 4150 - Adjust new tests to workspace names --- .../02-ChangeNodeAggregateName.feature | 18 +++++++++--------- .../Features/NodeTraversal/Timestamps.feature | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/02-ChangeNodeAggregateName.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/02-ChangeNodeAggregateName.feature index a8507e73c56..457b44cb3bf 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/02-ChangeNodeAggregateName.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/09-NodeRenaming/02-ChangeNodeAggregateName.feature @@ -24,7 +24,7 @@ Feature: Change node aggregate name | workspaceDescription | "The live workspace" | | newContentStreamId | "cs-identifier" | And the graph projection is fully up to date - And I am in the active content stream of workspace "live" and dimension space point {"example":"source"} + And I am in workspace "live" and dimension space point {"example":"source"} And the command CreateRootNodeAggregateWithNode is executed with payload: | Key | Value | @@ -68,22 +68,22 @@ Feature: Change node aggregate name And I expect the graph projection to consist of exactly 9 nodes - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"general"} Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"source"} Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"source"} Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"source"} - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"source"} Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"source"} Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"source"} - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"peer"} Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"peer"} Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to no node @@ -114,22 +114,22 @@ Feature: Change node aggregate name And I expect the graph projection to consist of exactly 9 nodes - When I am in the active content stream of workspace "live" and dimension space point {"example": "general"} + When I am in workspace "live" and dimension space point {"example": "general"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"general"} Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"general"} Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to no node - When I am in the active content stream of workspace "live" and dimension space point {"example": "source"} + When I am in workspace "live" and dimension space point {"example": "source"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"source"} Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"source"} Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"source"} - When I am in the active content stream of workspace "live" and dimension space point {"example": "spec"} + When I am in workspace "live" and dimension space point {"example": "spec"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "parent-document/renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"source"} Then I expect node aggregate identifier "nodimus-prime" and node path "parent-document/renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"source"} Then I expect node aggregate identifier "nodimus-mediocre" and node path "parent-document/renamed-document/tethered/grandchild-document" to lead to node cs-identifier;nodimus-mediocre;{"example":"source"} - When I am in the active content stream of workspace "live" and dimension space point {"example": "peer"} + When I am in workspace "live" and dimension space point {"example": "peer"} Then I expect node aggregate identifier "nody-mc-nodeface" and node path "renamed-document" to lead to node cs-identifier;nody-mc-nodeface;{"example":"peer"} Then I expect node aggregate identifier "nodimus-prime" and node path "renamed-document/tethered" to lead to node cs-identifier;nodimus-prime;{"example":"peer"} Then I expect node aggregate identifier "nodimus-mediocre" and node path "renamed-document/tethered/grandchild-document" to lead to no node diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature index f197b533c52..4487f84325f 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature +++ b/Neos.ContentRepository.BehavioralTests/Tests/Behavior/Features/NodeTraversal/Timestamps.feature @@ -149,32 +149,32 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la | newNodeName | "a-renamed" | And the graph projection is fully up to date - And I am in the active content stream of workspace "user-test" and dimension space point {"language":"de"} + And I am in workspace "user-test" and dimension space point {"language":"de"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 13:00:00 | 2023-03-16 12:00:00 | 2023-03-16 14:00:00 | 2023-03-16 14:00:00 | - And I am in the active content stream of workspace "user-test" and dimension space point {"language":"ch"} + And I am in workspace "user-test" and dimension space point {"language":"ch"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 13:00:00 | 2023-03-16 12:30:00 | 2023-03-16 14:00:00 | 2023-03-16 14:00:00 | - When I am in the active content stream of workspace "review" and dimension space point {"language":"de"} + When I am in workspace "review" and dimension space point {"language":"de"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 13:00:00 | 2023-03-16 12:00:00 | | | - When I am in the active content stream of workspace "review" and dimension space point {"language":"ch"} + When I am in workspace "review" and dimension space point {"language":"ch"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 13:00:00 | 2023-03-16 12:30:00 | | | - When I am in the active content stream of workspace "live" and dimension space point {"language":"de"} + When I am in workspace "live" and dimension space point {"language":"de"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 13:00:00 | 2023-03-16 12:00:00 | | | - When I am in the active content stream of workspace "live" and dimension space point {"language":"ch"} + When I am in workspace "live" and dimension space point {"language":"ch"} Then I expect the node "a" to have the following timestamps: | created | originalCreated | lastModified | originalLastModified | | 2023-03-16 13:00:00 | 2023-03-16 12:30:00 | | | From 866c9e40cc45ea3b35b25e033ea3909161d4da32 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 16 Mar 2024 22:07:30 +0100 Subject: [PATCH 173/214] BUGFIX: Repair `flow contentgraphintegrity:runviolationdetection` Too few arguments to function ContentGraphIntegrityCommandController 0 passed --- ...ContentGraphIntegrityCommandController.php | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/ContentGraphIntegrityCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/ContentGraphIntegrityCommandController.php index dd10ce1d5a6..71683ca6422 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Command/ContentGraphIntegrityCommandController.php +++ b/Neos.ContentRepositoryRegistry/Classes/Command/ContentGraphIntegrityCommandController.php @@ -11,8 +11,12 @@ * source code. */ -use Neos\ContentRepository\Core\Projection\ContentGraph\ProjectionIntegrityViolationDetectionRunner; +use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory; +use Neos\ContentRepository\Core\Infrastructure\DbalClientInterface; +use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Error\Messages\Result; +use Neos\Flow\Annotations as Flow; use Neos\Flow\Cli\CommandController; final class ContentGraphIntegrityCommandController extends CommandController @@ -20,20 +24,22 @@ final class ContentGraphIntegrityCommandController extends CommandController private const OUTPUT_MODE_CONSOLE = 'console'; private const OUTPUT_MODE_LOG = 'log'; - private ProjectionIntegrityViolationDetectionRunner $detectionRunner; + #[Flow\Inject()] + protected DbalClientInterface $dbalClient; + #[Flow\Inject()] + protected ContentRepositoryRegistry $contentRepositoryRegistry; - public function __construct(ProjectionIntegrityViolationDetectionRunner $detectionRunner) + public function runViolationDetectionCommand(string $contentRepository = 'default', string $outputMode = null): void { - $this->detectionRunner = $detectionRunner; - parent::__construct(); - } + $detectionRunner = $this->contentRepositoryRegistry->buildService( + ContentRepositoryId::fromString($contentRepository), + new DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory($this->dbalClient) + ); - public function runViolationDetectionCommand(string $outputMode = null): void - { $outputMode = $this->resolveOutputMode($outputMode); /** @var Result $result */ - $result = $this->detectionRunner->run(); + $result = $detectionRunner->run(); switch ($outputMode) { case self::OUTPUT_MODE_CONSOLE: foreach ($result->getErrors() as $error) { From e07783c6f29dee2c562ea028dcab72d863ba983a Mon Sep 17 00:00:00 2001 From: Bernhard Schmitt Date: Sat, 11 May 2024 18:20:51 +0200 Subject: [PATCH 174/214] 4150 - use already defined workspace name --- .../Module/Administration/SitesController.php | 2 +- .../Classes/Domain/Service/SiteServiceInternals.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php index 06828e270da..0eef114fab8 100755 --- a/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php +++ b/Neos.Neos/Classes/Controller/Module/Administration/SitesController.php @@ -204,7 +204,7 @@ public function updateSiteAction(Site $site, $newSiteNodeName) } foreach ($contentRepository->getWorkspaceFinder()->findAll() as $workspace) { - $siteNodeAggregate = $contentRepository->getContentGraph(WorkspaceName::forLive())->findChildNodeAggregateByName( + $siteNodeAggregate = $contentRepository->getContentGraph($workspace->workspaceName)->findChildNodeAggregateByName( $sitesNode->nodeAggregateId, $site->getNodeName()->toNodeName() ); diff --git a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php index 062e7ea8f18..ae5d75bb1e5 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php +++ b/Neos.Neos/Classes/Domain/Service/SiteServiceInternals.php @@ -30,7 +30,6 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeVariantSelectionStrategy; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Neos\Domain\Exception\SiteNodeTypeIsInvalid; use Neos\Neos\Domain\Model\Site; use Neos\Neos\Domain\Model\SiteNodeName; @@ -96,10 +95,11 @@ public function createSiteNodeIfNotExists(Site $site, string $nodeTypeName): voi throw SiteNodeTypeIsInvalid::becauseItIsNotOfTypeSite(NodeTypeName::fromString($nodeTypeName)); } - $siteNodeAggregate = $this->contentRepository->getContentGraph(WorkspaceName::forLive())->findChildNodeAggregateByName( - $sitesNodeIdentifier, - $site->getNodeName()->toNodeName(), - ); + $siteNodeAggregate = $this->contentRepository->getContentGraph($liveWorkspace->workspaceName) + ->findChildNodeAggregateByName( + $sitesNodeIdentifier, + $site->getNodeName()->toNodeName(), + ); if ($siteNodeAggregate instanceof NodeAggregate) { // Site node already exists return; From 18f200f667cc75d24e37f9b0ce1c69d450138201 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 12 May 2024 18:58:18 +0200 Subject: [PATCH 175/214] TASK: WIP Prepare synchronous cr - `ContentRepository::handle()` will block by default --- .../Classes/CommandHandler/CommandResult.php | 64 ++----------------- .../Classes/EventStore/EventPersister.php | 8 +-- .../CatchUpTriggerWithSynchronousOption.php | 6 +- 3 files changed, 13 insertions(+), 65 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/CommandHandler/CommandResult.php b/Neos.ContentRepository.Core/Classes/CommandHandler/CommandResult.php index a3b1ab3701f..515309a4974 100644 --- a/Neos.ContentRepository.Core/Classes/CommandHandler/CommandResult.php +++ b/Neos.ContentRepository.Core/Classes/CommandHandler/CommandResult.php @@ -5,75 +5,23 @@ namespace Neos\ContentRepository\Core\CommandHandler; use Neos\ContentRepository\Core\ContentRepository; -use Neos\ContentRepository\Core\Projection\ProjectionInterface; -use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; -use Neos\EventStore\Model\Event\SequenceNumber; -use Neos\EventStore\Model\Event\Version; -use Neos\EventStore\Model\EventStore\CommitResult; /** - * Result of the {@see ContentRepository::handle()} method to be able to block until the projections were updated. + * Was the result of the {@see ContentRepository::handle()} method. + * Previously one would need this to be able to block until the projections were updated. * - * {@see PendingProjections} for a detailed explanation how the blocking works. + * This will no longer be required in the future see https://github.com/neos/neos-development-collection/pull/4988 * + * @deprecated this b/c layer will be removed with the next beta or before Neos 9 final release * @api */ final readonly class CommandResult { - public function __construct( - private PendingProjections $pendingProjections, - public CommitResult $commitResult, - ) { - } - - /** - * an empty command result which should not result in projection updates - * @return self - */ - public static function empty(): self - { - return new self( - PendingProjections::empty(), - new CommitResult( - Version::first(), - SequenceNumber::none() - ) - ); - } - /** - * Wait until all projections are up to date; i.e. have processed the events. - * - * @return void - * @api + * We block by default thus you must not call this method or use this legacy stub + * @deprecated this b/c layer will be removed with the next beta or before Neos 9 final release */ public function block(): void { - foreach ($this->pendingProjections->projections as $pendingProjection) { - $expectedSequenceNumber = $this->pendingProjections->getExpectedSequenceNumber($pendingProjection); - $this->blockProjection($pendingProjection, $expectedSequenceNumber); - } - } - - /** - * @param ProjectionInterface $projection - */ - private function blockProjection(ProjectionInterface $projection, SequenceNumber $expectedSequenceNumber): void - { - $attempts = 0; - while ($projection->getCheckpointStorage()->getHighestAppliedSequenceNumber()->value < $expectedSequenceNumber->value) { - usleep(50000); // 50000μs = 50ms - if (++$attempts > 100) { // 5 seconds - throw new \RuntimeException( - sprintf( - 'TIMEOUT while waiting for projection "%s" to catch up to sequence number %d ' . - '- check the error logs for details.', - $projection::class, - $expectedSequenceNumber->value - ), - 1550232279 - ); - } - } } } diff --git a/Neos.ContentRepository.Core/Classes/EventStore/EventPersister.php b/Neos.ContentRepository.Core/Classes/EventStore/EventPersister.php index 4cc945eddcb..aa0e2abed44 100644 --- a/Neos.ContentRepository.Core/Classes/EventStore/EventPersister.php +++ b/Neos.ContentRepository.Core/Classes/EventStore/EventPersister.php @@ -11,8 +11,6 @@ use Neos\ContentRepository\Core\Projection\WithMarkStaleInterface; use Neos\EventStore\EventStoreInterface; use Neos\EventStore\Exception\ConcurrencyException; -use Neos\EventStore\Model\Event; -use Neos\EventStore\Model\Event\EventId; use Neos\EventStore\Model\Events; /** @@ -39,7 +37,7 @@ public function __construct( public function publishEvents(EventsToPublish $eventsToPublish): CommandResult { if ($eventsToPublish->events->isEmpty()) { - return CommandResult::empty(); + return new CommandResult(); } // the following logic could also be done in an AppEventStore::commit method (being called // directly from the individual Command Handlers). @@ -66,8 +64,6 @@ public function publishEvents(EventsToPublish $eventsToPublish): CommandResult } } $this->projectionCatchUpTrigger->triggerCatchUp($pendingProjections->projections); - - // The CommandResult can be used to block until projections are up to date. - return new CommandResult($pendingProjections, $commitResult); + return new CommandResult(); } } diff --git a/Neos.ContentRepositoryRegistry/Classes/Factory/ProjectionCatchUpTrigger/CatchUpTriggerWithSynchronousOption.php b/Neos.ContentRepositoryRegistry/Classes/Factory/ProjectionCatchUpTrigger/CatchUpTriggerWithSynchronousOption.php index 2a9e68d90dc..d6b5342d1e9 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Factory/ProjectionCatchUpTrigger/CatchUpTriggerWithSynchronousOption.php +++ b/Neos.ContentRepositoryRegistry/Classes/Factory/ProjectionCatchUpTrigger/CatchUpTriggerWithSynchronousOption.php @@ -23,6 +23,7 @@ * We will hopefully get rid of this class at some point; by introducing a NodeAggregate * which will take care of constraint enforcement then. * + * @deprecated remove me https://github.com/neos/neos-development-collection/pull/4988 * @internal */ class CatchUpTriggerWithSynchronousOption implements ProjectionCatchUpTriggerInterface @@ -33,7 +34,10 @@ class CatchUpTriggerWithSynchronousOption implements ProjectionCatchUpTriggerInt */ protected $contentRepositoryRegistry; - private static bool $synchronousEnabled = false; + /** + * Hack by setting to true to be always sync mode: https://github.com/neos/neos-development-collection/pull/4988 + */ + private static bool $synchronousEnabled = true; /** * INTERNAL From 3f1457e48575dbd01445c19dd777f843b451d99f Mon Sep 17 00:00:00 2001 From: Jenkins Date: Mon, 13 May 2024 07:04:13 +0000 Subject: [PATCH 176/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index 5e81e6834b0..961fd7cfd57 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-05-11 +The following reference was automatically generated from code on 2024-05-13 .. _`Neos Command Reference: NEOS.FLOW`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index 444e3873b39..0eca090ca26 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-05-11 +This reference was automatically generated from code on 2024-05-13 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index b1477b8a178..112048a38ef 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-05-11 +This reference was automatically generated from code on 2024-05-13 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index 64f0051c183..bdb546f8804 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-05-11 +This reference was automatically generated from code on 2024-05-13 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index 50b53880cbb..4b384a5911a 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-05-11 +This reference was automatically generated from code on 2024-05-13 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index 6e4e1f89bee..aee59c26eb9 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-05-11 +This reference was automatically generated from code on 2024-05-13 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From b46cd011c3c506dae84c9f1d6428aedec0b9f4bc Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 12 May 2024 13:08:32 +0200 Subject: [PATCH 177/214] FEATURE: Introduce Node access FlowQuery operations see https://github.com/neos/neos-development-collection/issues/5022 --- .../FlowQueryOperations/IdOperation.php | 77 ++++++++++++++++ .../FlowQueryOperations/LabelOperation.php | 77 ++++++++++++++++ .../NodeTypeNameOperation.php | 90 +++++++++++++++++++ .../ExpressionBasedNodeLabelGenerator.php | 4 +- .../Features/Fusion/FlowQuery.feature | 46 ++++++++++ 5 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/IdOperation.php create mode 100644 Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php create mode 100644 Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/NodeTypeNameOperation.php diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/IdOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/IdOperation.php new file mode 100644 index 00000000000..82d92207987 --- /dev/null +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/IdOperation.php @@ -0,0 +1,77 @@ + $context $context onto which this operation should be applied (array or array-like object) + * @return boolean + */ + public function canEvaluate($context): bool + { + return (isset($context[0]) && $context[0] instanceof Node); + } + + /** + * {@inheritdoc} + * + * @param FlowQuery $flowQuery the FlowQuery object + * @param array $arguments the arguments for this operation + * @return mixed + * @throws FlowQueryException + */ + public function evaluate(FlowQuery $flowQuery, array $arguments) + { + if ($arguments !== []) { + throw new FlowQueryException(static::$shortName . '() does not require any argument.', 1715510778); + } + $node = $flowQuery->getContext()[0] ?? null; + if (!$node instanceof Node) { + return null; + } + return $node->nodeAggregateId->value; + } +} diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php new file mode 100644 index 00000000000..3790819e345 --- /dev/null +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php @@ -0,0 +1,77 @@ + $context $context onto which this operation should be applied (array or array-like object) + * @return boolean + */ + public function canEvaluate($context): bool + { + return (isset($context[0]) && $context[0] instanceof Node); + } + + /** + * {@inheritdoc} + * + * @param FlowQuery $flowQuery the FlowQuery object + * @param array $arguments the arguments for this operation + * @return mixed + * @throws FlowQueryException + */ + public function evaluate(FlowQuery $flowQuery, array $arguments) + { + if ($arguments !== []) { + throw new FlowQueryException(static::$shortName . '() does not require any argument.', 1715510778); + } + $node = $flowQuery->getContext()[0] ?? null; + if (!$node instanceof Node) { + return null; + } + return $node->getLabel(); + } +} diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/NodeTypeNameOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/NodeTypeNameOperation.php new file mode 100644 index 00000000000..4664a8c5009 --- /dev/null +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/NodeTypeNameOperation.php @@ -0,0 +1,90 @@ + $context $context onto which this operation should be applied (array or array-like object) + * @return boolean + */ + public function canEvaluate($context): bool + { + return (isset($context[0]) && $context[0] instanceof Node); + } + + /** + * {@inheritdoc} + * + * @param FlowQuery $flowQuery the FlowQuery object + * @param array $arguments the arguments for this operation + * @return mixed + * @throws FlowQueryException + */ + public function evaluate(FlowQuery $flowQuery, array $arguments) + { + if ($arguments !== []) { + throw new FlowQueryException(static::$shortName . '() does not require any argument.', 1715510778); + } + $node = $flowQuery->getContext()[0] ?? null; + if (!$node instanceof Node) { + return null; + } + + $contentRepository = $this->contentRepositoryRegistry->get($node->subgraphIdentity->contentRepositoryId); + + return $contentRepository->getNodeTypeManager()->hasNodeType($node->nodeTypeName) + ? $node->nodeTypeName + : NodeTypeNameFactory::forFallback()->value; + } +} diff --git a/Neos.ContentRepositoryRegistry/Classes/NodeLabel/ExpressionBasedNodeLabelGenerator.php b/Neos.ContentRepositoryRegistry/Classes/NodeLabel/ExpressionBasedNodeLabelGenerator.php index 8ed9333b6a0..0babece2680 100644 --- a/Neos.ContentRepositoryRegistry/Classes/NodeLabel/ExpressionBasedNodeLabelGenerator.php +++ b/Neos.ContentRepositoryRegistry/Classes/NodeLabel/ExpressionBasedNodeLabelGenerator.php @@ -37,7 +37,9 @@ class ExpressionBasedNodeLabelGenerator implements NodeLabelGeneratorInterface /** * @var string */ - protected $expression = '${(node.nodeType.label ? node.nodeType.label : node.nodeType.name) + \' (\' + node.name + \')\'}'; + protected $expression = <<<'EEL' + ${(node.nodeType.label ? node.nodeType.label : node.nodeType.name) + (node.nodeName ? ' (' + node.nodeName.value + ')' : '')} + EEL; /** * @return string diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature index b7073940274..5681e0390f0 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature @@ -395,3 +395,49 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. removeNode: a nothingToRemove: a1a4,a1a4,a1a4 """ + + Scenario: Node accessors (final Node access operations) + When the Fusion context node is "a1" + When I execute the following Fusion code: + """fusion + test = Neos.Fusion:DataStructure { + property = ${q(node).property('title')} + identifier = ${q(node).id()} + label = ${q(node).label()} + nodeTypeName = ${q(node).nodeTypeName()} + @process.render = ${Json.stringify(value, ['JSON_PRETTY_PRINT'])} + } + """ + Then I expect the following Fusion rendering result: + """ + { + "property": "Node a1", + "identifier": "a1", + "label": "Neos.Neos:Test.DocumentType1", + "nodeTypeName": "Neos.Neos:Test.DocumentType1" + } + """ + # changing the node type config will invoke the Neos.Neos:FallbackNode handling + When I change the node types in content repository "default" to: + """yaml + 'Neos.Neos:FallbackNode': {} + """ + When I execute the following Fusion code: + """fusion + test = Neos.Fusion:DataStructure { + property = ${q(node).property('title')} + identifier = ${q(node).id()} + label = ${q(node).label()} + nodeTypeName = ${q(node).nodeTypeName()} + @process.render = ${Json.stringify(value, ['JSON_PRETTY_PRINT'])} + } + """ + Then I expect the following Fusion rendering result: + """ + { + "property": "Node a1", + "identifier": "a1", + "label": "Neos.Neos:Test.DocumentType1", + "nodeTypeName": "Neos.Neos:FallbackNode" + } + """ From 540c605cb174b0e70e44377fe0702da3e4193aef Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 13:57:21 +0200 Subject: [PATCH 178/214] TASK: `q(node).nodeTypeName()` should not respect fallback node type --- .../FlowQueryOperations/NodeTypeNameOperation.php | 14 +------------- .../FlowQueryOperations/PropertyOperation.php | 9 +++++---- Neos.Neos/Classes/Fusion/Helper/NodeHelper.php | 6 ++++++ .../Private/Fusion/Prototypes/ContentCase.fusion | 3 +-- .../Behavior/Features/Fusion/FlowQuery.feature | 5 ++--- 5 files changed, 15 insertions(+), 22 deletions(-) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/NodeTypeNameOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/NodeTypeNameOperation.php index 4664a8c5009..77f62e6373f 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/NodeTypeNameOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/NodeTypeNameOperation.php @@ -12,12 +12,9 @@ */ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; -use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Eel\FlowQuery\FlowQuery; use Neos\Eel\FlowQuery\FlowQueryException; use Neos\Eel\FlowQuery\Operations\AbstractOperation; -use Neos\Flow\Annotations as Flow; -use Neos\Neos\Domain\Service\NodeTypeNameFactory; /** * Used to access the NodeTypeName of a ContentRepository Node. @@ -45,11 +42,6 @@ class NodeTypeNameOperation extends AbstractOperation */ protected static $final = true; - /** - * @Flow\Inject - */ - protected ContentRepositoryRegistry $contentRepositoryRegistry; - /** * {@inheritdoc} * @@ -81,10 +73,6 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) return null; } - $contentRepository = $this->contentRepositoryRegistry->get($node->subgraphIdentity->contentRepositoryId); - - return $contentRepository->getNodeTypeManager()->hasNodeType($node->nodeTypeName) - ? $node->nodeTypeName - : NodeTypeNameFactory::forFallback()->value; + return $node->nodeTypeName->value; } } diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php index fe612a2cd5f..840a963cb19 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/PropertyOperation.php @@ -57,8 +57,6 @@ class PropertyOperation extends AbstractOperation */ protected $contentRepositoryRegistry; - use NodeTypeWithFallbackProvider; - /** * {@inheritdoc} * @@ -113,7 +111,10 @@ public function evaluate(FlowQuery $flowQuery, array $arguments): mixed return ObjectAccess::getPropertyPath($element, substr($propertyName, 1)); } - if ($this->getNodeType($element)->hasReference($propertyName)) { + $contentRepository = $this->contentRepositoryRegistry->get($element->subgraphIdentity->contentRepositoryId); + $nodeTypeManager = $contentRepository->getNodeTypeManager(); + + if ($nodeTypeManager->getNodeType($element->nodeTypeName)?->hasReference($propertyName)) { // legacy access layer for references $subgraph = $this->contentRepositoryRegistry->subgraphForNode($element); $references = $subgraph->findReferences( @@ -121,7 +122,7 @@ public function evaluate(FlowQuery $flowQuery, array $arguments): mixed FindReferencesFilter::create(referenceName: $propertyName) )->getNodes(); - $maxItems = $this->getNodeType($element)->getReferences()[$propertyName]['constraints']['maxItems'] ?? null; + $maxItems = $nodeTypeManager->getNodeType($element->nodeTypeName)->getReferences()[$propertyName]['constraints']['maxItems'] ?? null; if ($maxItems === 1) { // legacy layer references with only one item like the previous `type: reference` // (the node type transforms that to constraints.maxItems = 1) diff --git a/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php b/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php index 28de5534bb7..6fe98b9f9dc 100644 --- a/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php @@ -142,6 +142,12 @@ public function getNodeType(Node $node): NodeType return $this->getNodeTypeInternal($node); } + public function isNodeTypeExistent(Node $node): bool + { + $contentRepository = $this->contentRepositoryRegistry->get($node->subgraphIdentity->contentRepositoryId); + return $contentRepository->getNodeTypeManager()->hasNodeType($node->nodeTypeName); + } + public function serializedNodeAddress(Node $node): string { $contentRepository = $this->contentRepositoryRegistry->get( diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/ContentCase.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/ContentCase.fusion index fde834c17cc..951e396dfa6 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/ContentCase.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/ContentCase.fusion @@ -6,7 +6,6 @@ prototype(Neos.Neos:ContentCase) < prototype(Neos.Fusion:Case) { default { @position = 'end' condition = true - # this eel helper also handles the Neos.Neos:FallbackNode node type mechanism - type = ${Neos.Node.getNodeType(node).name.value} + type = ${Neos.Node.isNodeTypeExistent(node) ? q(node).nodeTypeName() : 'Neos.Neos:FallbackNode'} } } diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature index 5681e0390f0..f8bbbfc3a90 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature @@ -417,10 +417,9 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. "nodeTypeName": "Neos.Neos:Test.DocumentType1" } """ - # changing the node type config will invoke the Neos.Neos:FallbackNode handling + # if the node type config is empty, the operation should still work When I change the node types in content repository "default" to: """yaml - 'Neos.Neos:FallbackNode': {} """ When I execute the following Fusion code: """fusion @@ -438,6 +437,6 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. "property": "Node a1", "identifier": "a1", "label": "Neos.Neos:Test.DocumentType1", - "nodeTypeName": "Neos.Neos:FallbackNode" + "nodeTypeName": "Neos.Neos:Test.DocumentType1" } """ From 5ba50e996a915dff97dd6bd281b5a4a494885cff Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 14:40:36 +0200 Subject: [PATCH 179/214] TASK: Fix linting warnings --- .../Classes/FlowQueryOperations/IdOperation.php | 4 +++- .../Classes/FlowQueryOperations/LabelOperation.php | 4 +++- .../Classes/FlowQueryOperations/NodeTypeNameOperation.php | 5 +++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/IdOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/IdOperation.php index 82d92207987..d98ad6ee3c8 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/IdOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/IdOperation.php @@ -68,7 +68,9 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) if ($arguments !== []) { throw new FlowQueryException(static::$shortName . '() does not require any argument.', 1715510778); } - $node = $flowQuery->getContext()[0] ?? null; + /** @var array $context */ + $context = $flowQuery->getContext(); + $node = $context[0] ?? null; if (!$node instanceof Node) { return null; } diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php index 3790819e345..2023ce109d2 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php @@ -68,7 +68,9 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) if ($arguments !== []) { throw new FlowQueryException(static::$shortName . '() does not require any argument.', 1715510778); } - $node = $flowQuery->getContext()[0] ?? null; + /** @var array $context */ + $context = $flowQuery->getContext(); + $node = $context[0] ?? null; if (!$node instanceof Node) { return null; } diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/NodeTypeNameOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/NodeTypeNameOperation.php index 77f62e6373f..82d5770c315 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/NodeTypeNameOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/NodeTypeNameOperation.php @@ -68,11 +68,12 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) if ($arguments !== []) { throw new FlowQueryException(static::$shortName . '() does not require any argument.', 1715510778); } - $node = $flowQuery->getContext()[0] ?? null; + /** @var array $context */ + $context = $flowQuery->getContext(); + $node = $context[0] ?? null; if (!$node instanceof Node) { return null; } - return $node->nodeTypeName->value; } } From 9d067b1c93d6fee1f974292f39334edcb51eb610 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:05:30 +0200 Subject: [PATCH 180/214] !!! TASK: Refactor node label rendering (as pure Neos' concept) The CR core will not know anything about node labels anymore. This will be a pure Neos' attached concept. --- ...yStringNodeBasedNodeTypeManagerFactory.php | 17 +---- .../WorkspaceWritingDuringPublication.php | 4 +- .../DefaultNodeLabelGeneratorFactory.php | 26 -------- .../NodeLabelGeneratorFactoryInterface.php | 29 --------- .../Classes/NodeType/NodeType.php | 19 +----- .../Classes/NodeType/NodeTypeManager.php | 6 +- .../NodeTypeNameNodeLabelGenerator.php | 30 --------- .../Classes/Projection/ContentGraph/Node.php | 1 + .../Dto/NodeAggregateIdsByNodePathsTest.php | 4 +- .../Unit/NodeType/NodeTypeManagerTest.php | 10 +-- .../Tests/Unit/NodeType/NodeTypeTest.php | 63 +++++++++---------- .../DefaultNodeTypeManagerFactory.php | 4 +- .../Configuration/Objects.yaml | 3 - .../ExpressionBasedNodeLabelGenerator.php | 3 +- .../NodeLabelGeneratorInterface.php | 2 +- .../Domain/NodeLabel/NodeLabelRenderer.php | 8 +-- .../Functional/Fusion/NodeHelperTest.php | 9 +-- .../Service/NodeTypeSchemaBuilderTest.php | 5 +- ...DefaultPropertyEditorPostprocessorTest.php | 4 +- 19 files changed, 49 insertions(+), 198 deletions(-) delete mode 100644 Neos.ContentRepository.Core/Classes/NodeType/DefaultNodeLabelGeneratorFactory.php delete mode 100644 Neos.ContentRepository.Core/Classes/NodeType/NodeLabelGeneratorFactoryInterface.php delete mode 100644 Neos.ContentRepository.Core/Classes/NodeType/NodeTypeNameNodeLabelGenerator.php rename {Neos.ContentRepositoryRegistry/Classes => Neos.Neos/Classes/Domain}/NodeLabel/ExpressionBasedNodeLabelGenerator.php (93%) rename {Neos.ContentRepository.Core/Classes/NodeType => Neos.Neos/Classes/Domain/NodeLabel}/NodeLabelGeneratorInterface.php (92%) rename Neos.ContentRepositoryRegistry/Classes/Factory/NodeTypeManager/ObjectManagerBasedNodeLabelGeneratorFactory.php => Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php (80%) diff --git a/Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/GherkinPyStringNodeBasedNodeTypeManagerFactory.php b/Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/GherkinPyStringNodeBasedNodeTypeManagerFactory.php index cc91ca70224..82796bf1738 100644 --- a/Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/GherkinPyStringNodeBasedNodeTypeManagerFactory.php +++ b/Neos.ContentRepository.BehavioralTests/Classes/TestSuite/Behavior/GherkinPyStringNodeBasedNodeTypeManagerFactory.php @@ -15,11 +15,7 @@ namespace Neos\ContentRepository\BehavioralTests\TestSuite\Behavior; use Behat\Gherkin\Node\PyStringNode; -use Neos\ContentRepository\Core\NodeType\NodeLabelGeneratorFactoryInterface; -use Neos\ContentRepository\Core\NodeType\NodeLabelGeneratorInterface; -use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; -use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepositoryRegistry\Factory\NodeTypeManager\NodeTypeManagerFactoryInterface; use Symfony\Component\Yaml\Yaml; @@ -45,18 +41,7 @@ public function build(ContentRepositoryId $contentRepositoryId, array $options): public static function initializeWithPyStringNode(PyStringNode $nodeTypesToUse): void { self::$nodeTypesToUse = new NodeTypeManager( - fn (): array => Yaml::parse($nodeTypesToUse->getRaw()) ?? [], - new class implements NodeLabelGeneratorFactoryInterface { - public function create(NodeType $nodeType): NodeLabelGeneratorInterface - { - return new class implements NodeLabelGeneratorInterface { - public function getLabel(Node $node): string - { - return $node->nodeTypeName->value; - } - }; - } - } + fn (): array => Yaml::parse($nodeTypesToUse->getRaw()) ?? [] ); } diff --git a/Neos.ContentRepository.BehavioralTests/Tests/Functional/Feature/WorkspacePublication/WorkspaceWritingDuringPublication.php b/Neos.ContentRepository.BehavioralTests/Tests/Functional/Feature/WorkspacePublication/WorkspaceWritingDuringPublication.php index 4d532e7a805..74b80f5bcde 100644 --- a/Neos.ContentRepository.BehavioralTests/Tests/Functional/Feature/WorkspacePublication/WorkspaceWritingDuringPublication.php +++ b/Neos.ContentRepository.BehavioralTests/Tests/Functional/Feature/WorkspacePublication/WorkspaceWritingDuringPublication.php @@ -31,7 +31,6 @@ use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateRootWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceCreation\Command\CreateWorkspace; use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Command\RebaseWorkspace; -use Neos\ContentRepository\Core\NodeType\DefaultNodeLabelGeneratorFactory; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; @@ -90,8 +89,7 @@ public function getContentDimensionsOrderedByPriority(): array ] ] ] - ], - new DefaultNodeLabelGeneratorFactory() + ] ); $this->contentRepositoryRegistry = $this->objectManager->get(ContentRepositoryRegistry::class); diff --git a/Neos.ContentRepository.Core/Classes/NodeType/DefaultNodeLabelGeneratorFactory.php b/Neos.ContentRepository.Core/Classes/NodeType/DefaultNodeLabelGeneratorFactory.php deleted file mode 100644 index 3a577d176b4..00000000000 --- a/Neos.ContentRepository.Core/Classes/NodeType/DefaultNodeLabelGeneratorFactory.php +++ /dev/null @@ -1,26 +0,0 @@ -name = $name; $this->declaredSuperTypes = $declaredSuperTypes; @@ -396,20 +393,6 @@ public function getOptions(): array return ($this->fullConfiguration['options'] ?? []); } - /** - * Return the node label generator class for the given node - */ - public function getNodeLabelGenerator(): NodeLabelGeneratorInterface - { - $this->initialize(); - - if ($this->nodeLabelGenerator === null) { - $this->nodeLabelGenerator = $this->nodeLabelGeneratorFactory->create($this); - } - - return $this->nodeLabelGenerator; - } - /** * Return the array with the defined properties. The key is the property name, * the value the property configuration. There are no guarantees on how the diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php index 4c98cdf9383..7ade13450ab 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php @@ -46,8 +46,7 @@ final class NodeTypeManager * @internal */ public function __construct( - private readonly \Closure $nodeTypeConfigLoader, - private readonly NodeLabelGeneratorFactoryInterface $nodeLabelGeneratorFactory + private readonly \Closure $nodeTypeConfigLoader ) { } @@ -319,8 +318,7 @@ private function loadNodeType(string $nodeTypeName, array &$completeNodeTypeConf $nodeType = new NodeType( NodeTypeName::fromString($nodeTypeName), $superTypes, - $nodeTypeConfiguration, - $this->nodeLabelGeneratorFactory + $nodeTypeConfiguration ); $this->cachedNodeTypes[$nodeTypeName] = $nodeType; diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeNameNodeLabelGenerator.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeNameNodeLabelGenerator.php deleted file mode 100644 index 9a87f7b2d6c..00000000000 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeNameNodeLabelGenerator.php +++ /dev/null @@ -1,30 +0,0 @@ -nodeTypeName->value, \mb_strrpos($node->nodeTypeName->value, '.') + 1); - } -} diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index 48250a8107c..b1410dddf21 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -108,6 +108,7 @@ public function hasProperty(string $propertyName): bool */ public function getLabel(): string { + // todo REMOVE ME!!!!!!!!!!!! return $this->nodeType?->getNodeLabelGenerator()->getLabel($this) ?: $this->nodeTypeName->value; } diff --git a/Neos.ContentRepository.Core/Tests/Unit/Feature/NodeCreation/Dto/NodeAggregateIdsByNodePathsTest.php b/Neos.ContentRepository.Core/Tests/Unit/Feature/NodeCreation/Dto/NodeAggregateIdsByNodePathsTest.php index 04d9f727cf6..cc4a645daf6 100644 --- a/Neos.ContentRepository.Core/Tests/Unit/Feature/NodeCreation/Dto/NodeAggregateIdsByNodePathsTest.php +++ b/Neos.ContentRepository.Core/Tests/Unit/Feature/NodeCreation/Dto/NodeAggregateIdsByNodePathsTest.php @@ -15,7 +15,6 @@ 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; @@ -56,8 +55,7 @@ public function testCompleteForNodeOfType(NodeAggregateIdsByNodePaths $subject, ] ] ] - ], - new DefaultNodeLabelGeneratorFactory() + ] ); $completeSubject = $subject->completeForNodeOfType( diff --git a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeManagerTest.php b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeManagerTest.php index fc5de248c5b..db140498d82 100644 --- a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeManagerTest.php +++ b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeManagerTest.php @@ -11,7 +11,6 @@ * source code. */ -use Neos\ContentRepository\Core\NodeType\DefaultNodeLabelGeneratorFactory; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; @@ -43,8 +42,7 @@ public function setUp(): void protected function prepareNodeTypeManager(array $nodeTypesFixtureData) { $this->nodeTypeManager = new NodeTypeManager( - fn() => $nodeTypesFixtureData, - new DefaultNodeLabelGeneratorFactory() + fn() => $nodeTypesFixtureData ); } @@ -447,8 +445,7 @@ public function getAutoCreatedChildNodesReturnsLowercaseNames() public function rootNodeTypeIsAlwaysPresent() { $nodeTypeManager = new NodeTypeManager( - fn() => [], - new DefaultNodeLabelGeneratorFactory() + fn() => [] ); self::assertTrue($nodeTypeManager->hasNodeType(NodeTypeName::ROOT_NODE_TYPE_NAME)); self::assertInstanceOf(NodeType::class, $nodeTypeManager->getNodeType(NodeTypeName::ROOT_NODE_TYPE_NAME)); @@ -460,8 +457,7 @@ public function rootNodeTypeIsAlwaysPresent() public function rootNodeTypeIsPresentAfterOverride() { $nodeTypeManager = new NodeTypeManager( - fn() => [], - new DefaultNodeLabelGeneratorFactory() + fn() => [] ); $nodeTypeManager->overrideNodeTypes(['Some:NewNodeType' => []]); self::assertTrue($nodeTypeManager->hasNodeType(NodeTypeName::fromString('Some:NewNodeType'))); diff --git a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php index 45a346cd05e..7d3df5c6c46 100644 --- a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php +++ b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php @@ -12,7 +12,6 @@ * source code. */ -use Neos\ContentRepository\Core\NodeType\DefaultNodeLabelGeneratorFactory; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\SharedModel\Exception\NodeConfigurationException; @@ -129,7 +128,7 @@ class NodeTypeTest extends TestCase */ public function aNodeTypeHasAName() { - $nodeType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository.Testing:Text'), [], [], new DefaultNodeLabelGeneratorFactory()); + $nodeType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository.Testing:Text'), [], []); self::assertSame('Neos.ContentRepository.Testing:Text', $nodeType->name->value); } @@ -147,7 +146,7 @@ public function aNodeTypeMustHaveDistinctNamesForPropertiesReferences() 'references' => [ 'foo' => [] ] - ], new DefaultNodeLabelGeneratorFactory()); + ]); $this->expectException(NodeConfigurationException::class); $this->expectExceptionCode(1708022344); // initialize the node type @@ -165,12 +164,14 @@ public function aNodeTypeMustHaveDistinctNamesForPropertiesReferencesInInheritan 'type' => 'string', ] ] - ], new DefaultNodeLabelGeneratorFactory()); - $nodeType = new NodeType(NodeTypeName::fromString('ContentRepository:Invalid'), ['ContentRepository:Super' => $superNodeType], [ - 'references' => [ - 'foo' => [] - ] - ], new DefaultNodeLabelGeneratorFactory()); + ]); + $nodeType = new NodeType(NodeTypeName::fromString('ContentRepository:Invalid'), + ['ContentRepository:Super' => $superNodeType], + [ + 'references' => [ + 'foo' => [] + ] + ]); $this->expectException(NodeConfigurationException::class); $this->expectExceptionCode(1708022344); // initialize the node type @@ -182,11 +183,11 @@ public function aNodeTypeMustHaveDistinctNamesForPropertiesReferencesInInheritan */ public function nodeTypesCanHaveAnyNumberOfSuperTypes() { - $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], new DefaultNodeLabelGeneratorFactory()); + $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], []); $timeableNodeType = new NodeType( NodeTypeName::fromString('Neos.ContentRepository.Testing:TimeableContent'), - [], [], new DefaultNodeLabelGeneratorFactory() + [], [] ); $documentType = new NodeType( NodeTypeName::fromString('Neos.ContentRepository.Testing:Document'), @@ -194,12 +195,12 @@ public function nodeTypesCanHaveAnyNumberOfSuperTypes() 'Neos.ContentRepository:Base' => $baseType, 'Neos.ContentRepository.Testing:TimeableContent' => $timeableNodeType, ], - [], new DefaultNodeLabelGeneratorFactory() + [] ); $hideableNodeType = new NodeType( NodeTypeName::fromString('Neos.ContentRepository.Testing:HideableContent'), - [], [], new DefaultNodeLabelGeneratorFactory() + [], [] ); $pageType = new NodeType( NodeTypeName::fromString('Neos.ContentRepository.Testing:Page'), @@ -208,8 +209,7 @@ public function nodeTypesCanHaveAnyNumberOfSuperTypes() 'Neos.ContentRepository.Testing:HideableContent' => $hideableNodeType, 'Neos.ContentRepository.Testing:TimeableContent' => null, ], - [], - new DefaultNodeLabelGeneratorFactory() + [] ); self::assertEquals( @@ -234,7 +234,7 @@ public function nodeTypesCanHaveAnyNumberOfSuperTypes() */ public function labelIsEmptyStringByDefault() { - $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], new DefaultNodeLabelGeneratorFactory()); + $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], []); self::assertSame('', $baseType->getLabel()); } @@ -243,7 +243,7 @@ public function labelIsEmptyStringByDefault() */ public function propertiesAreEmptyArrayByDefault() { - $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], new DefaultNodeLabelGeneratorFactory()); + $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], []); self::assertSame([], $baseType->getProperties()); } @@ -256,7 +256,7 @@ public function hasConfigurationReturnsTrueIfSpecifiedConfigurationPathExists() 'someKey' => [ 'someSubKey' => 'someValue' ] - ], new DefaultNodeLabelGeneratorFactory()); + ]); self::assertTrue($nodeType->hasConfiguration('someKey.someSubKey')); } @@ -265,7 +265,7 @@ public function hasConfigurationReturnsTrueIfSpecifiedConfigurationPathExists() */ public function hasConfigurationReturnsFalseIfSpecifiedConfigurationPathDoesNotExist() { - $nodeType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], new DefaultNodeLabelGeneratorFactory()); + $nodeType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], []); self::assertFalse($nodeType->hasConfiguration('some.nonExisting.path')); } @@ -278,7 +278,7 @@ public function getConfigurationReturnsTheConfigurationWithTheSpecifiedPath() 'someKey' => [ 'someSubKey' => 'someValue' ] - ], new DefaultNodeLabelGeneratorFactory()); + ]); self::assertSame('someValue', $nodeType->getConfiguration('someKey.someSubKey')); } @@ -287,7 +287,7 @@ public function getConfigurationReturnsTheConfigurationWithTheSpecifiedPath() */ public function getConfigurationReturnsNullIfTheSpecifiedPathDoesNotExist() { - $nodeType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], new DefaultNodeLabelGeneratorFactory()); + $nodeType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], []); self::assertNull($nodeType->getConfiguration('some.nonExisting.path')); } @@ -403,7 +403,7 @@ public function propertyDeclaration() 'defaultValue' => false ] ] - ], new DefaultNodeLabelGeneratorFactory()); + ]); self::assertTrue($nodeType->hasProperty('someProperty')); self::assertFalse($nodeType->hasReference('someProperty')); self::assertSame('bool', $nodeType->getPropertyType('someProperty')); @@ -429,7 +429,7 @@ public function getPropertyTypeThrowsOnInvalidProperty() { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionCode(1708025421); - $nodeType = new NodeType(NodeTypeName::fromString('ContentRepository:Node'), [], [], new DefaultNodeLabelGeneratorFactory()); + $nodeType = new NodeType(NodeTypeName::fromString('ContentRepository:Node'), [], []); $nodeType->getPropertyType('nonExistent'); self::assertSame('string', $nodeType->getPropertyType('nonExistent')); } @@ -443,7 +443,7 @@ public function getPropertyTypeFallback() 'properties' => [ 'someProperty' => [] ] - ], new DefaultNodeLabelGeneratorFactory()); + ]); self::assertSame('string', $nodeType->getPropertyType('someProperty')); } @@ -466,7 +466,7 @@ public function getDefaultValuesForPropertiesIgnoresNullAndUnset() 'type' => 'string' ] ] - ], new DefaultNodeLabelGeneratorFactory()); + ]); self::assertSame(['someProperty' => 'lol'], $nodeType->getDefaultValuesForProperties()); } @@ -479,7 +479,7 @@ public function referencesDeclaration() 'references' => [ 'someReferences' => [] ] - ], new DefaultNodeLabelGeneratorFactory()); + ]); self::assertFalse($nodeType->hasProperty('someReferences')); self::assertTrue($nodeType->hasReference('someReferences')); self::assertThrows(fn() => $nodeType->getPropertyType('someReferences'), \InvalidArgumentException::class); @@ -506,7 +506,7 @@ public function legacyPropertyReferenceDeclaration() 'type' => 'reference', ] ] - ], new DefaultNodeLabelGeneratorFactory()); + ]); // will be available as _real_ reference self::assertFalse($nodeType->hasProperty('referenceProperty')); self::assertTrue($nodeType->hasReference('referenceProperty')); @@ -538,7 +538,7 @@ public function legacyPropertyReferencesDeclaration() 'type' => 'references', ] ] - ], new DefaultNodeLabelGeneratorFactory()); + ]); // will be available as _real_ reference self::assertFalse($nodeType->hasProperty('referencesProperty')); self::assertTrue($nodeType->hasReference('referencesProperty')); @@ -569,7 +569,7 @@ public function legacyPropertyReferencesDeclarationMustNotUseConstraintFeatures( ], ] ] - ], new DefaultNodeLabelGeneratorFactory()); + ]); $this->expectException(NodeConfigurationException::class); $this->expectExceptionCode(1708022344); $nodeType->getReferences(); @@ -591,7 +591,7 @@ public function legacyPropertyReferencesDeclarationMustNotUsePropertiesFeatures( ], ] ] - ], new DefaultNodeLabelGeneratorFactory()); + ]); $this->expectException(NodeConfigurationException::class); $this->expectExceptionCode(1708022344); $nodeType->getReferences(); @@ -618,8 +618,7 @@ protected function getNodeType(string $nodeTypeName): ?NodeType return new NodeType( NodeTypeName::fromString($nodeTypeName), $declaredSuperTypes, - $configuration, - new DefaultNodeLabelGeneratorFactory() + $configuration ); } diff --git a/Neos.ContentRepositoryRegistry/Classes/Factory/NodeTypeManager/DefaultNodeTypeManagerFactory.php b/Neos.ContentRepositoryRegistry/Classes/Factory/NodeTypeManager/DefaultNodeTypeManagerFactory.php index cc888aa6e13..2b1d783ec05 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Factory/NodeTypeManager/DefaultNodeTypeManagerFactory.php +++ b/Neos.ContentRepositoryRegistry/Classes/Factory/NodeTypeManager/DefaultNodeTypeManagerFactory.php @@ -11,7 +11,6 @@ { public function __construct( private ConfigurationManager $configurationManager, - private ObjectManagerBasedNodeLabelGeneratorFactory $nodeLabelGeneratorFactory, private NodeTypeEnrichmentService $nodeTypeEnrichmentService, ) { } @@ -23,8 +22,7 @@ public function build(ContentRepositoryId $contentRepositoryId, array $options): function () { $configuration = $this->configurationManager->getConfiguration('NodeTypes'); return $this->nodeTypeEnrichmentService->enrichNodeTypeLabelsConfiguration($configuration); - }, - $this->nodeLabelGeneratorFactory + } ); } } diff --git a/Neos.ContentRepositoryRegistry/Configuration/Objects.yaml b/Neos.ContentRepositoryRegistry/Configuration/Objects.yaml index 89c3069794e..854fb3dc508 100644 --- a/Neos.ContentRepositoryRegistry/Configuration/Objects.yaml +++ b/Neos.ContentRepositoryRegistry/Configuration/Objects.yaml @@ -3,9 +3,6 @@ Neos\ContentRepositoryRegistry\ContentRepositoryRegistry: 1: setting: Neos.ContentRepositoryRegistry -Neos\ContentRepository\Core\NodeType\NodeLabelGeneratorFactoryInterface: - className: Neos\ContentRepositoryRegistry\Factory\NodeTypeManager\ObjectManagerBasedNodeLabelGeneratorFactory - # !!! UGLY WORKAROUNDS, because we cannot wire non-Flow class constructor arguments here. Neos\ContentRepository\Core\Projection\ContentStream\ContentStreamProjectionFactory: scope: singleton diff --git a/Neos.ContentRepositoryRegistry/Classes/NodeLabel/ExpressionBasedNodeLabelGenerator.php b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php similarity index 93% rename from Neos.ContentRepositoryRegistry/Classes/NodeLabel/ExpressionBasedNodeLabelGenerator.php rename to Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php index 0babece2680..df2ae66a8ca 100644 --- a/Neos.ContentRepositoryRegistry/Classes/NodeLabel/ExpressionBasedNodeLabelGenerator.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php @@ -1,5 +1,5 @@ hasConfiguration('label.generatorClass')) { $nodeLabelGeneratorClassName = $nodeType->getConfiguration('label.generatorClass'); $nodeLabelGenerator = $this->objectManager->get($nodeLabelGeneratorClassName); diff --git a/Neos.Neos/Tests/Functional/Fusion/NodeHelperTest.php b/Neos.Neos/Tests/Functional/Fusion/NodeHelperTest.php index fc7b35fd719..cc239a574de 100644 --- a/Neos.Neos/Tests/Functional/Fusion/NodeHelperTest.php +++ b/Neos.Neos/Tests/Functional/Fusion/NodeHelperTest.php @@ -15,9 +15,7 @@ use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValue; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; -use Neos\ContentRepository\Core\NodeType\DefaultNodeLabelGeneratorFactory; use Neos\ContentRepository\Core\NodeType\NodeType; -use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphIdentity; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; @@ -134,12 +132,7 @@ protected function setUp(): void 'ui' => [ 'label' => 'Content.Text' ] - ], - new NodeTypeManager( - fn () => [], - new DefaultNodeLabelGeneratorFactory() - ), - new DefaultNodeLabelGeneratorFactory() + ] ); $textNodeProperties = new PropertyCollection( diff --git a/Neos.Neos/Tests/Functional/Service/NodeTypeSchemaBuilderTest.php b/Neos.Neos/Tests/Functional/Service/NodeTypeSchemaBuilderTest.php index f5c64ffef50..a71db4cd5fe 100644 --- a/Neos.Neos/Tests/Functional/Service/NodeTypeSchemaBuilderTest.php +++ b/Neos.Neos/Tests/Functional/Service/NodeTypeSchemaBuilderTest.php @@ -11,7 +11,6 @@ * source code. */ -use Neos\ContentRepository\Core\NodeType\NodeLabelGeneratorFactoryInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\Flow\Configuration\ConfigurationManager; use Neos\Flow\Tests\FunctionalTestCase; @@ -38,11 +37,9 @@ public function setUp(): void { parent::setUp(); $configurationManager = $this->objectManager->get(ConfigurationManager::class); - $nodeLabelGeneratorFactory = $this->objectManager->get(NodeLabelGeneratorFactoryInterface::class); $this->nodeTypeSchemaBuilder = NodeTypeSchemaBuilder::create( new NodeTypeManager( - fn() => $configurationManager->getConfiguration('NodeTypes'), - $nodeLabelGeneratorFactory + fn() => $configurationManager->getConfiguration('NodeTypes') ) ); $this->schema = $this->nodeTypeSchemaBuilder->generateNodeTypeSchema(); diff --git a/Neos.Neos/Tests/Unit/NodeTypePostprocessor/DefaultPropertyEditorPostprocessorTest.php b/Neos.Neos/Tests/Unit/NodeTypePostprocessor/DefaultPropertyEditorPostprocessorTest.php index dd2a83b674a..c1d55055280 100644 --- a/Neos.Neos/Tests/Unit/NodeTypePostprocessor/DefaultPropertyEditorPostprocessorTest.php +++ b/Neos.Neos/Tests/Unit/NodeTypePostprocessor/DefaultPropertyEditorPostprocessorTest.php @@ -12,7 +12,6 @@ * source code. */ -use Neos\ContentRepository\Core\NodeType\DefaultNodeLabelGeneratorFactory; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\Flow\Tests\UnitTestCase; @@ -344,8 +343,7 @@ private function processConfiguration(array $configuration, array $dataTypesDefa $mockNodeType = new NodeType( NodeTypeName::fromString('Some.NodeType:Name'), [], - [], - new DefaultNodeLabelGeneratorFactory() + [] ); $postprocessor->process($mockNodeType, $configuration, []); return $configuration; From 6d75f33b51841e5fee2e4dc4d47a8d03ee8f2bdb Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:25:23 +0200 Subject: [PATCH 181/214] !!! TASK: Replace `Node::getLabel` with `NodeLabelRenderer` and `Neos.Node.renderLabel(node)` --- .../Classes/Projection/ContentGraph/Node.php | 11 -------- .../ExpressionBasedNodeLabelGenerator.php | 2 +- .../Domain/NodeLabel/NodeLabelRenderer.php | 26 ++++++++++++++++--- .../DimensionsMenuItemsImplementation.php | 7 ++++- .../Classes/Fusion/Helper/NodeHelper.php | 18 ++++++++++++- .../Fusion/MenuItemsImplementation.php | 9 +++++-- .../Utility/NodeUriPathSegmentGenerator.php | 6 ++++- .../ViewHelpers/Link/NodeViewHelper.php | 9 ++++++- .../Private/Fusion/Prototypes/NodeLink.fusion | 2 +- .../Fusion/RawContent/NodeHeader.fusion | 2 +- ...imeableNodeVisibilityCommandController.php | 10 +++---- .../Service/TimeableNodeVisibilityService.php | 6 ++--- 12 files changed, 76 insertions(+), 32 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index b1410dddf21..299c73134c4 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -101,17 +101,6 @@ public function hasProperty(string $propertyName): bool return $this->properties->offsetExists($propertyName); } - /** - * Returns the node label as generated by the configured node label generator - * - * @return string - */ - public function getLabel(): string - { - // todo REMOVE ME!!!!!!!!!!!! - return $this->nodeType?->getNodeLabelGenerator()->getLabel($this) ?: $this->nodeTypeName->value; - } - public function equals(Node $other): bool { return $this->subgraphIdentity->equals($other->subgraphIdentity) diff --git a/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php index df2ae66a8ca..2369b61a2a7 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php @@ -1,4 +1,5 @@ getNodeType($node); + $generator = $this->getNodeLabelGeneratorForNodeType($nodeType); + return $generator->getLabel($node); + } + + /** + * @internal + */ + public function getNodeLabelGeneratorForNodeType(NodeType $nodeType): NodeLabelGeneratorInterface { if ($nodeType->hasConfiguration('label.generatorClass')) { $nodeLabelGeneratorClassName = $nodeType->getConfiguration('label.generatorClass'); diff --git a/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php index 42b514c6f8a..4a90c5f1e32 100644 --- a/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php @@ -10,6 +10,8 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; +use Neos\Flow\Annotations as Flow; +use Neos\Neos\Domain\NodeLabel\NodeLabelRenderer; /** * Fusion implementation for a dimensions menu. @@ -28,6 +30,9 @@ */ class DimensionsMenuItemsImplementation extends AbstractMenuItemsImplementation { + #[Flow\Inject()] + protected NodeLabelRenderer $nodeLabelRenderer; + /** * @return array */ @@ -215,7 +220,7 @@ protected function determineLabel(?Node $variant = null, array $metadata = []): if ($this->getContentDimensionIdentifierToLimitTo()) { return $metadata[$this->getContentDimensionIdentifierToLimitTo()->value]['label'] ?: ''; } elseif ($variant) { - return $variant->getLabel() ?: ''; + return $this->nodeLabelRenderer->renderNodeLabel($variant) ?: ''; } else { return array_reduce($metadata, function ($carry, $item) { return $carry . (empty($carry) ? '' : '-') . $item['label']; diff --git a/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php b/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php index 6fe98b9f9dc..92b0e71523c 100644 --- a/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php @@ -25,6 +25,7 @@ use Neos\Eel\ProtectedContextAwareInterface; use Neos\Flow\Annotations as Flow; use Neos\Neos\Domain\Exception; +use Neos\Neos\Domain\NodeLabel\NodeLabelRenderer; use Neos\Neos\Domain\Service\NodeTypeNameFactory; use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\Neos\Presentation\VisualNodePath; @@ -42,6 +43,9 @@ class NodeHelper implements ProtectedContextAwareInterface #[Flow\Inject] protected ContentRepositoryRegistry $contentRepositoryRegistry; + #[Flow\Inject] + protected NodeLabelRenderer $nodeLabelRenderer; + /** * Check if the given node is already a collection, find collection by nodePath otherwise, throw exception * if no content collection could be found @@ -96,13 +100,25 @@ public function nearestContentCollection(Node $node, string $nodePath): Node } /** - * Generate a label for a node with a chaining mechanism. To be used in nodetype definitions. + * Generate a label for a node with a chaining mechanism. To be used in NodeType definition: + * + * 'Vendor.Site:MyContent': + * label: "${Neos.Node.labelForNode(node).prefix('foo')}" + * */ public function labelForNode(Node $node): NodeLabelToken { return new NodeLabelToken($node); } + /** + * Renders the actual node label based on the NodeType definition in Fusion. + */ + public function renderLabel(Node $node): string + { + return $this->nodeLabelRenderer->renderNodeLabel($node); + } + /** * @param Node $node * @return int diff --git a/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php index 4bee1c0ec52..bea39003373 100644 --- a/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php @@ -24,7 +24,9 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Subtree; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; +use Neos\Flow\Annotations as Flow; use Neos\Fusion\Exception as FusionException; +use Neos\Neos\Domain\NodeLabel\NodeLabelRenderer; use Neos\Neos\Domain\Service\NodeTypeNameFactory; /** @@ -62,6 +64,9 @@ class MenuItemsImplementation extends AbstractMenuItemsImplementation */ protected ?NodeTypeCriteria $nodeTypeCriteria = null; + #[Flow\Inject()] + protected NodeLabelRenderer $nodeLabelRenderer; + /** * The last navigation level which should be rendered. * @@ -215,7 +220,7 @@ protected function buildMenuItemFromNode(Node $node): MenuItem return new MenuItem( $node, $this->isCalculateItemStatesEnabled() ? $this->calculateItemState($node) : null, - $node->getLabel(), + $this->nodeLabelRenderer->renderNodeLabel($node), 0, [], $this->buildUri($node) @@ -239,7 +244,7 @@ protected function buildMenuItemFromSubtree(Subtree $subtree, int $startLevel = return new MenuItem( $node, $this->isCalculateItemStatesEnabled() ? $this->calculateItemState($node) : null, - $node->getLabel(), + $this->nodeLabelRenderer->renderNodeLabel($node), $subtree->level + $startLevel, $children, $this->buildUri($node) diff --git a/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php b/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php index 33a24f941f1..cbecf791fb7 100644 --- a/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php +++ b/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php @@ -18,6 +18,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\Flow\Annotations as Flow; use Neos\Flow\I18n\Locale; +use Neos\Neos\Domain\NodeLabel\NodeLabelRenderer; use Neos\Neos\Exception; use Neos\Neos\Service\TransliterationService; @@ -30,6 +31,9 @@ class NodeUriPathSegmentGenerator #[Flow\Inject] protected TransliterationService $transliterationService; + #[Flow\Inject] + protected NodeLabelRenderer $nodeLabelRenderer; + /** * Generates a URI path segment for a given node taking its language dimension value into account * @@ -40,7 +44,7 @@ public function generateUriPathSegment(?Node $node = null, ?string $text = null) { $language = null; if ($node) { - $text = $text ?: $node->getLabel() ?: ($node->nodeName?->value ?? ''); + $text = $text ?: $this->nodeLabelRenderer->renderNodeLabel($node) ?: ($node->nodeName?->value ?? ''); $languageDimensionValue = $node->originDimensionSpacePoint->coordinates['language'] ?? null; if (!is_null($languageDimensionValue)) { $locale = new Locale($languageDimensionValue); diff --git a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php index dab61d2c975..8a8e569954b 100644 --- a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php @@ -20,6 +20,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; +use Neos\Neos\Domain\NodeLabel\NodeLabelRenderer; use Neos\Neos\Domain\Service\NodeTypeNameFactory; use Neos\Neos\FrontendRouting\NodeAddress; use Neos\Neos\FrontendRouting\NodeAddressFactory; @@ -149,6 +150,12 @@ class NodeViewHelper extends AbstractTagBasedViewHelper */ protected $throwableStorage; + /** + * @Flow\Inject + * @var NodeLabelRenderer + */ + protected $nodeLabelRenderer; + /** * Initialize arguments * @@ -340,7 +347,7 @@ public function render(): string $this->templateVariableContainer->remove($this->arguments['nodeVariableName']); if ($content === null && $resolvedNode !== null) { - $content = $resolvedNode->getLabel(); + $content = $this->nodeLabelRenderer->renderNodeLabel($resolvedNode); } $this->tag->setContent($content); diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/NodeLink.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/NodeLink.fusion index d64b2cccef0..ce3f2b09f50 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/NodeLink.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/NodeLink.fusion @@ -33,5 +33,5 @@ prototype(Neos.Neos:NodeLink) < prototype(Neos.Fusion:Tag) { } } - content = ${node.label} + content = ${Neos.Node.renderLabel(node)} } diff --git a/Neos.Neos/Resources/Private/Fusion/RawContent/NodeHeader.fusion b/Neos.Neos/Resources/Private/Fusion/RawContent/NodeHeader.fusion index aa3b1b01d9e..60afbb73ed2 100644 --- a/Neos.Neos/Resources/Private/Fusion/RawContent/NodeHeader.fusion +++ b/Neos.Neos/Resources/Private/Fusion/RawContent/NodeHeader.fusion @@ -11,7 +11,7 @@ prototype(Neos.Neos:RawContent.NodeHeader) < prototype(Neos.Fusion:Component) { renderer = afx`
  - {(node.tethered || q(node).is('[instanceof Neos.Neos:Document]')) ? node.label : Translation.translate(props.labelParts[2], node.nodeType.label, [], props.labelParts[1], props.labelParts[0])} + {(node.tethered || q(node).is('[instanceof Neos.Neos:Document]')) ? Neos.Node.renderLabel(node) : Translation.translate(props.labelParts[2], node.nodeType.label, [], props.labelParts[1], props.labelParts[0])} - {props.visibilityInformations}
` diff --git a/Neos.TimeableNodeVisibility/Classes/Command/TimeableNodeVisibilityCommandController.php b/Neos.TimeableNodeVisibility/Classes/Command/TimeableNodeVisibilityCommandController.php index 56624a83685..aa90155bdb2 100644 --- a/Neos.TimeableNodeVisibility/Classes/Command/TimeableNodeVisibilityCommandController.php +++ b/Neos.TimeableNodeVisibility/Classes/Command/TimeableNodeVisibilityCommandController.php @@ -28,10 +28,9 @@ public function executeCommand(string $contentRepository = 'default', bool $quie $this->output->outputLine(sprintf('Enabled %d nodes with exceeded timed dates.', $handlingResult->countByType(ChangedVisibilityType::NODE_WAS_ENABLED))); foreach ($handlingResult->getByType(ChangedVisibilityType::NODE_WAS_ENABLED) as $result) { $this->output->outputLine(sprintf( - '- NodeAggregateId: %s, DimensionSpacePoint: %s, Label: %s', + '- NodeAggregateId: %s, DimensionSpacePoint: %s', $result->node->nodeAggregateId->value, - join(',', $result->node->originDimensionSpacePoint->coordinates), - $result->node->getLabel() + join(',', $result->node->originDimensionSpacePoint->coordinates) ) ); } @@ -39,10 +38,9 @@ public function executeCommand(string $contentRepository = 'default', bool $quie $this->output->outputLine(sprintf('Disabled %d nodes with exceeded timed dates.', $handlingResult->countByType(ChangedVisibilityType::NODE_WAS_DISABLED))); foreach ($handlingResult->getByType(ChangedVisibilityType::NODE_WAS_DISABLED) as $result) { $this->output->outputLine(sprintf( - '- NodeAggregateId: %s, DimensionSpacePoint: %s, Label: %s', + '- NodeAggregateId: %s, DimensionSpacePoint: %s', $result->node->nodeAggregateId->value, - join(',', $result->node->originDimensionSpacePoint->coordinates), - $result->node->getLabel() + join(',', $result->node->originDimensionSpacePoint->coordinates) ) ); } diff --git a/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php b/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php index 70056016538..3731e7546bb 100644 --- a/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php +++ b/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php @@ -162,11 +162,11 @@ private function needsDisabling(Node $node, \DateTimeImmutable $now): bool private function logResult(ChangedVisibility $result): void { $this->logger->info( - sprintf('Timed node visibility: %s node [NodeAggregateId: %s, DimensionSpacePoints: %s]: %s', + sprintf('Timed node visibility: %s node [NodeAggregateId: %s, DimensionSpacePoints: %s]', $result->type->value, $result->node->nodeAggregateId->value, - implode(',', $result->node->originDimensionSpacePoint->coordinates), - $result->node->getLabel()) + implode(',', $result->node->originDimensionSpacePoint->coordinates) + ) ); } } From c6f3f4a53277a67f42f0bc77cb3aa318ca2ee837 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 1 May 2024 11:10:23 +0200 Subject: [PATCH 182/214] !!! TASK: Move `Neos.ContentRepository.labelGenerator.eel.defaultContext` to `Neos.Neos` ... and remove dead configuration from early Neos 9 experiments --- .../Configuration/Settings.yaml | 52 ------------------- .../ExpressionBasedNodeLabelGenerator.php | 2 +- Neos.Neos/Configuration/Settings.yaml | 40 ++++---------- 3 files changed, 10 insertions(+), 84 deletions(-) delete mode 100644 Neos.ContentRepository.NodeAccess/Configuration/Settings.yaml diff --git a/Neos.ContentRepository.NodeAccess/Configuration/Settings.yaml b/Neos.ContentRepository.NodeAccess/Configuration/Settings.yaml deleted file mode 100644 index 34c0b75a38a..00000000000 --- a/Neos.ContentRepository.NodeAccess/Configuration/Settings.yaml +++ /dev/null @@ -1,52 +0,0 @@ -Neos: - - ContentRepository: - - # Configure available content dimensions for nodes, after adding a dimension the database has to be filled with - # the dimension default values. - # Also add named presets with fallback chains that can happen in your dimensions. - # - # Example - # - # contentDimensions: - # language: - # default: mul_ZZ - # defaultPreset: 'all' - # presets: - # 'all': - # values: ['mul_ZZ'] - # - # persona: - # default: everybody - # defaultPreset: 'all' - # presets: - # 'all': - # values: ['everybody'] - contentDimensions: { } - - # Configures defaults for node label generation - labelGenerator: - eel: - defaultContext: - String: Neos\Eel\Helper\StringHelper - Array: Neos\Eel\Helper\ArrayHelper - Date: Neos\Eel\Helper\DateHelper - Configuration: Neos\Eel\Helper\ConfigurationHelper - Math: Neos\Eel\Helper\MathHelper - Json: Neos\Eel\Helper\JsonHelper - I18n: Neos\Flow\I18n\EelHelper\TranslationHelper - q: Neos\Eel\FlowQuery\FlowQuery::q - - unstableInternalWillChangeLater: - projection: - defaultProjectorsToBeBlocked: - 'Neos\ContentRepository\Core\Projection\ContentStream\ContentStreamProjector': true - 'Neos\ContentRepository\Core\Projection\Workspace\WorkspaceProjector': true - - - NodeAccess: - nodeAccessorFactories: - contentSubgraph: - position: end - className: Neos\ContentRepository\NodeAccess\NodeAccessor\Implementation\ContentSubgraphAccessorFactory - diff --git a/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php index 2369b61a2a7..ebe21834ef5 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php @@ -28,7 +28,7 @@ class ExpressionBasedNodeLabelGenerator implements NodeLabelGeneratorInterface protected EelEvaluatorInterface $eelEvaluator; /** - * @Flow\InjectConfiguration(package="Neos.ContentRepository", path="labelGenerator.eel.defaultContext") + * @Flow\InjectConfiguration(package="Neos.Neos", path="labelGenerator.eel.defaultContext") * @var array */ protected $defaultContextConfiguration; diff --git a/Neos.Neos/Configuration/Settings.yaml b/Neos.Neos/Configuration/Settings.yaml index 76419846d02..0561ad4c399 100755 --- a/Neos.Neos/Configuration/Settings.yaml +++ b/Neos.Neos/Configuration/Settings.yaml @@ -471,40 +471,18 @@ Neos: neos-site: Sites neos-plugin: Plugins - ContentRepository: - # Definition of available content dimensions. Additional content dimensions may be defined in third-party packages - # or via global settings. - # - #contentDimensions: - # - # # Content dimension "language" serves for translation of content into different languages. Its value specifies - # # the language or language variant by means of a locale. - # 'language': - # # The default dimension that is applied when creating nodes without specifying a dimension - # default: 'mul_ZZ' - # # The default preset to use if no URI segment was given when resolving languages in the router - # defaultPreset: 'all' - # label: 'Language' - # icon: 'icon-language' - # presets: - # 'all': - # label: 'All languages' - # values: ['mul_ZZ'] - # uriSegment: 'all' - # # Example for additional languages: - # - # 'en_GB': - # label: 'English (Great Britain)' - # values: ['en_GB', 'en_ZZ', 'mul_ZZ'] - # uriSegment: 'gb' - # 'de': - # label: 'German (Germany)' - # values: ['de_DE', 'de_ZZ', 'mul_ZZ'] - # uriSegment: 'de' - + # Configures defaults for node label generation labelGenerator: eel: defaultContext: + String: Neos\Eel\Helper\StringHelper + Array: Neos\Eel\Helper\ArrayHelper + Date: Neos\Eel\Helper\DateHelper + Configuration: Neos\Eel\Helper\ConfigurationHelper + Math: Neos\Eel\Helper\MathHelper + Json: Neos\Eel\Helper\JsonHelper + I18n: Neos\Flow\I18n\EelHelper\TranslationHelper + q: Neos\Eel\FlowQuery\FlowQuery::q Neos.Node: Neos\Neos\Fusion\Helper\NodeHelper Fusion: From 2a2672281348b2a72e835889674508fc8c73a162 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 14:16:43 +0200 Subject: [PATCH 183/214] TASK: Replace `Neos.Node.renderLabel` with `q(node).label()` --- .../Classes/Domain/NodeLabel/NodeLabelRenderer.php | 3 +-- Neos.Neos/Classes/Fusion/Helper/NodeHelper.php | 12 ------------ .../Private/Fusion/Prototypes/NodeLink.fusion | 2 +- .../Private/Fusion/RawContent/NodeHeader.fusion | 2 +- 4 files changed, 3 insertions(+), 16 deletions(-) diff --git a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php index 2a3a7a47b07..047a9167812 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php @@ -8,11 +8,10 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\ObjectManagement\ObjectManagerInterface; -use Neos\Neos\Fusion\Helper\NodeHelper; use Neos\Neos\Utility\NodeTypeWithFallbackProvider; /** - * @api For PHP, in Fusion one can use ${Neos.Node.renderLabel(node)} {@see NodeHelper::renderLabel()} + * @api For PHP, in Fusion one can use ${q(node).label()} to access the label */ final readonly class NodeLabelRenderer { diff --git a/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php b/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php index 92b0e71523c..33c5e84c6ce 100644 --- a/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/NodeHelper.php @@ -25,7 +25,6 @@ use Neos\Eel\ProtectedContextAwareInterface; use Neos\Flow\Annotations as Flow; use Neos\Neos\Domain\Exception; -use Neos\Neos\Domain\NodeLabel\NodeLabelRenderer; use Neos\Neos\Domain\Service\NodeTypeNameFactory; use Neos\Neos\FrontendRouting\NodeAddressFactory; use Neos\Neos\Presentation\VisualNodePath; @@ -43,9 +42,6 @@ class NodeHelper implements ProtectedContextAwareInterface #[Flow\Inject] protected ContentRepositoryRegistry $contentRepositoryRegistry; - #[Flow\Inject] - protected NodeLabelRenderer $nodeLabelRenderer; - /** * Check if the given node is already a collection, find collection by nodePath otherwise, throw exception * if no content collection could be found @@ -111,14 +107,6 @@ public function labelForNode(Node $node): NodeLabelToken return new NodeLabelToken($node); } - /** - * Renders the actual node label based on the NodeType definition in Fusion. - */ - public function renderLabel(Node $node): string - { - return $this->nodeLabelRenderer->renderNodeLabel($node); - } - /** * @param Node $node * @return int diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/NodeLink.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/NodeLink.fusion index ce3f2b09f50..c064f1131f5 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/NodeLink.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/NodeLink.fusion @@ -33,5 +33,5 @@ prototype(Neos.Neos:NodeLink) < prototype(Neos.Fusion:Tag) { } } - content = ${Neos.Node.renderLabel(node)} + content = ${q(node).label()} } diff --git a/Neos.Neos/Resources/Private/Fusion/RawContent/NodeHeader.fusion b/Neos.Neos/Resources/Private/Fusion/RawContent/NodeHeader.fusion index 60afbb73ed2..741e0b095f1 100644 --- a/Neos.Neos/Resources/Private/Fusion/RawContent/NodeHeader.fusion +++ b/Neos.Neos/Resources/Private/Fusion/RawContent/NodeHeader.fusion @@ -11,7 +11,7 @@ prototype(Neos.Neos:RawContent.NodeHeader) < prototype(Neos.Fusion:Component) { renderer = afx`
  - {(node.tethered || q(node).is('[instanceof Neos.Neos:Document]')) ? Neos.Node.renderLabel(node) : Translation.translate(props.labelParts[2], node.nodeType.label, [], props.labelParts[1], props.labelParts[0])} + {(node.tethered || q(node).is('[instanceof Neos.Neos:Document]')) ? q(node).label() : Translation.translate(props.labelParts[2], node.nodeType.label, [], props.labelParts[1], props.labelParts[0])} - {props.visibilityInformations}
` From 10b7db1e386b7a70114e64f557588a16f621d7e1 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 14:31:09 +0200 Subject: [PATCH 184/214] TASK: Introduce `NodeLabelRendererInterface` --- .../Classes/FlowQueryOperations/LabelOperation.php | 7 ++++++- .../Classes/Domain/NodeLabel/NodeLabelRenderer.php | 11 +++++------ .../Domain/NodeLabel/NodeLabelRendererInterface.php | 13 +++++++++++++ .../Fusion/DimensionsMenuItemsImplementation.php | 4 ++-- .../Classes/Fusion/MenuItemsImplementation.php | 4 ++-- .../Classes/Utility/NodeUriPathSegmentGenerator.php | 4 ++-- .../Classes/ViewHelpers/Link/NodeViewHelper.php | 4 ++-- 7 files changed, 32 insertions(+), 15 deletions(-) create mode 100644 Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRendererInterface.php diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php index 2023ce109d2..07daf8a5773 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php @@ -15,6 +15,8 @@ use Neos\Eel\FlowQuery\FlowQuery; use Neos\Eel\FlowQuery\FlowQueryException; use Neos\Eel\FlowQuery\Operations\AbstractOperation; +use Neos\Flow\Annotations as Flow; +use Neos\Neos\Domain\NodeLabel\NodeLabelRendererInterface; /** * Used to access the Node's label of a ContentRepository Node. @@ -42,6 +44,9 @@ class LabelOperation extends AbstractOperation */ protected static $final = true; + #[Flow\Inject()] + protected NodeLabelRendererInterface $nodeLabelRenderer; + /** * {@inheritdoc} * @@ -74,6 +79,6 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) if (!$node instanceof Node) { return null; } - return $node->getLabel(); + return $this->nodeLabelRenderer->renderNodeLabel($node); } } diff --git a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php index 047a9167812..b3a9fdb06a3 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php @@ -7,13 +7,15 @@ use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; +use Neos\Flow\Annotations as Flow; use Neos\Flow\ObjectManagement\ObjectManagerInterface; use Neos\Neos\Utility\NodeTypeWithFallbackProvider; /** - * @api For PHP, in Fusion one can use ${q(node).label()} to access the label + * @internal please reference the interface {@see NodeLabelRendererInterface} instead. */ -final readonly class NodeLabelRenderer +#[Flow\Scope('singleton')] +final readonly class NodeLabelRenderer implements NodeLabelRendererInterface { use NodeTypeWithFallbackProvider; @@ -30,10 +32,7 @@ public function renderNodeLabel(Node $node): string return $generator->getLabel($node); } - /** - * @internal - */ - public function getNodeLabelGeneratorForNodeType(NodeType $nodeType): NodeLabelGeneratorInterface + private function getNodeLabelGeneratorForNodeType(NodeType $nodeType): NodeLabelGeneratorInterface { if ($nodeType->hasConfiguration('label.generatorClass')) { $nodeLabelGeneratorClassName = $nodeType->getConfiguration('label.generatorClass'); diff --git a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRendererInterface.php b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRendererInterface.php new file mode 100644 index 00000000000..784df637f58 --- /dev/null +++ b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRendererInterface.php @@ -0,0 +1,13 @@ + diff --git a/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php index bea39003373..a0a50066de9 100644 --- a/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php @@ -26,7 +26,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\Flow\Annotations as Flow; use Neos\Fusion\Exception as FusionException; -use Neos\Neos\Domain\NodeLabel\NodeLabelRenderer; +use Neos\Neos\Domain\NodeLabel\NodeLabelRendererInterface; use Neos\Neos\Domain\Service\NodeTypeNameFactory; /** @@ -65,7 +65,7 @@ class MenuItemsImplementation extends AbstractMenuItemsImplementation protected ?NodeTypeCriteria $nodeTypeCriteria = null; #[Flow\Inject()] - protected NodeLabelRenderer $nodeLabelRenderer; + protected NodeLabelRendererInterface $nodeLabelRenderer; /** * The last navigation level which should be rendered. diff --git a/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php b/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php index cbecf791fb7..23aebcae82f 100644 --- a/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php +++ b/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php @@ -18,7 +18,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\Flow\Annotations as Flow; use Neos\Flow\I18n\Locale; -use Neos\Neos\Domain\NodeLabel\NodeLabelRenderer; +use Neos\Neos\Domain\NodeLabel\NodeLabelRendererInterface; use Neos\Neos\Exception; use Neos\Neos\Service\TransliterationService; @@ -32,7 +32,7 @@ class NodeUriPathSegmentGenerator protected TransliterationService $transliterationService; #[Flow\Inject] - protected NodeLabelRenderer $nodeLabelRenderer; + protected NodeLabelRendererInterface $nodeLabelRenderer; /** * Generates a URI path segment for a given node taking its language dimension value into account diff --git a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php index 8a8e569954b..76ab8ab8117 100644 --- a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php @@ -20,7 +20,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; -use Neos\Neos\Domain\NodeLabel\NodeLabelRenderer; +use Neos\Neos\Domain\NodeLabel\NodeLabelRendererInterface; use Neos\Neos\Domain\Service\NodeTypeNameFactory; use Neos\Neos\FrontendRouting\NodeAddress; use Neos\Neos\FrontendRouting\NodeAddressFactory; @@ -152,7 +152,7 @@ class NodeViewHelper extends AbstractTagBasedViewHelper /** * @Flow\Inject - * @var NodeLabelRenderer + * @var NodeLabelRendererInterface */ protected $nodeLabelRenderer; From 30b861fc39d8b725aa6f5b88a352141d64c4f6b2 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 14:34:04 +0200 Subject: [PATCH 185/214] TASK: Introduce `NodeLabel` value object --- .../FlowQueryOperations/LabelOperation.php | 2 +- .../ExpressionBasedNodeLabelGenerator.php | 7 ++++--- .../Classes/Domain/NodeLabel/NodeLabel.php | 18 ++++++++++++++++++ .../NodeLabel/NodeLabelGeneratorInterface.php | 2 +- .../Domain/NodeLabel/NodeLabelRenderer.php | 2 +- .../NodeLabel/NodeLabelRendererInterface.php | 2 +- .../DimensionsMenuItemsImplementation.php | 2 +- .../Classes/Fusion/MenuItemsImplementation.php | 4 ++-- .../Utility/NodeUriPathSegmentGenerator.php | 2 +- .../ViewHelpers/Link/NodeViewHelper.php | 2 +- 10 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 Neos.Neos/Classes/Domain/NodeLabel/NodeLabel.php diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php index 07daf8a5773..3ee887a90eb 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php @@ -79,6 +79,6 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) if (!$node instanceof Node) { return null; } - return $this->nodeLabelRenderer->renderNodeLabel($node); + return $this->nodeLabelRenderer->renderNodeLabel($node)->value; } } diff --git a/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php index ebe21834ef5..4a036814568 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php @@ -57,11 +57,12 @@ public function setExpression(string $expression): void * Render a node label based on an eel expression or return the expression if it is not valid eel. * @throws \Neos\Eel\Exception */ - public function getLabel(Node $node): string + public function getLabel(Node $node): NodeLabel { if (Utility::parseEelExpression($this->getExpression()) === null) { - return $this->getExpression(); + return NodeLabel::fromString($this->getExpression()); } - return (string)Utility::evaluateEelExpression($this->getExpression(), $this->eelEvaluator, ['node' => $node], $this->defaultContextConfiguration); + $value = Utility::evaluateEelExpression($this->getExpression(), $this->eelEvaluator, ['node' => $node], $this->defaultContextConfiguration); + return NodeLabel::fromString((string)$value); } } diff --git a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabel.php b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabel.php new file mode 100644 index 00000000000..372fd5de5d0 --- /dev/null +++ b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabel.php @@ -0,0 +1,18 @@ +getNodeType($node); $generator = $this->getNodeLabelGeneratorForNodeType($nodeType); diff --git a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRendererInterface.php b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRendererInterface.php index 784df637f58..390052f849b 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRendererInterface.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRendererInterface.php @@ -9,5 +9,5 @@ */ interface NodeLabelRendererInterface { - public function renderNodeLabel(Node $node): string; + public function renderNodeLabel(Node $node): NodeLabel; } diff --git a/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php index f2b19fd0b71..ef004181f87 100644 --- a/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php @@ -220,7 +220,7 @@ protected function determineLabel(?Node $variant = null, array $metadata = []): if ($this->getContentDimensionIdentifierToLimitTo()) { return $metadata[$this->getContentDimensionIdentifierToLimitTo()->value]['label'] ?: ''; } elseif ($variant) { - return $this->nodeLabelRenderer->renderNodeLabel($variant) ?: ''; + return $this->nodeLabelRenderer->renderNodeLabel($variant)->value ?: ''; } else { return array_reduce($metadata, function ($carry, $item) { return $carry . (empty($carry) ? '' : '-') . $item['label']; diff --git a/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php index a0a50066de9..360756f7dd1 100644 --- a/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php @@ -220,7 +220,7 @@ protected function buildMenuItemFromNode(Node $node): MenuItem return new MenuItem( $node, $this->isCalculateItemStatesEnabled() ? $this->calculateItemState($node) : null, - $this->nodeLabelRenderer->renderNodeLabel($node), + $this->nodeLabelRenderer->renderNodeLabel($node)->value, 0, [], $this->buildUri($node) @@ -244,7 +244,7 @@ protected function buildMenuItemFromSubtree(Subtree $subtree, int $startLevel = return new MenuItem( $node, $this->isCalculateItemStatesEnabled() ? $this->calculateItemState($node) : null, - $this->nodeLabelRenderer->renderNodeLabel($node), + $this->nodeLabelRenderer->renderNodeLabel($node)->value, $subtree->level + $startLevel, $children, $this->buildUri($node) diff --git a/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php b/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php index 23aebcae82f..b3f87c895d5 100644 --- a/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php +++ b/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php @@ -44,7 +44,7 @@ public function generateUriPathSegment(?Node $node = null, ?string $text = null) { $language = null; if ($node) { - $text = $text ?: $this->nodeLabelRenderer->renderNodeLabel($node) ?: ($node->nodeName?->value ?? ''); + $text = $text ?: $this->nodeLabelRenderer->renderNodeLabel($node)->value ?: ($node->nodeName?->value ?? ''); $languageDimensionValue = $node->originDimensionSpacePoint->coordinates['language'] ?? null; if (!is_null($languageDimensionValue)) { $locale = new Locale($languageDimensionValue); diff --git a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php index 76ab8ab8117..8bf0d9ca5c3 100644 --- a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php @@ -347,7 +347,7 @@ public function render(): string $this->templateVariableContainer->remove($this->arguments['nodeVariableName']); if ($content === null && $resolvedNode !== null) { - $content = $this->nodeLabelRenderer->renderNodeLabel($resolvedNode); + $content = $this->nodeLabelRenderer->renderNodeLabel($resolvedNode)->value; } $this->tag->setContent($content); From b687896cc55d2f4aabd328ab2a17ffdfdc03e305 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 14:37:39 +0200 Subject: [PATCH 186/214] TASK: Introduce b/c layer for `Node::getLabel()` --- .../Classes/Projection/ContentGraph/Node.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index 299c73134c4..fde990c64da 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -101,6 +101,28 @@ public function hasProperty(string $propertyName): bool return $this->properties->offsetExists($propertyName); } + /** + * Returned the node label as generated by the configured node label generator. + * + * In PHP please use Neos' {@see NodeLabelRendererInterface} instead. + * + * For Fusion please use the FlowQuery operation: + * ``` + * ${q(node).label()} + * ``` + * + * @deprecated will be removed before the final 9.0 release + */ + public function getLabel(): string + { + if (!class_exists(\Neos\Neos\Domain\NodeLabel\NodeLabelRenderer::class)) { + throw new \BadMethodCallException('node labels are removed from standalone cr.'); + } + // highly illegal + /** @phpstan-ignore-next-line */ + return (new \Neos\Neos\Domain\NodeLabel\NodeLabelRenderer())->renderNodeLabel($this)->value; + } + public function equals(Node $other): bool { return $this->subgraphIdentity->equals($other->subgraphIdentity) From 356456b6cab402183b278fccecd3c638c40573de Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 15:43:52 +0200 Subject: [PATCH 187/214] TASK: Fix label generation and tests --- .../ExpressionBasedNodeLabelGenerator.php | 2 +- .../Domain/NodeLabel/NodeLabelRenderer.php | 14 +++++----- Neos.Neos/Configuration/Settings.yaml | 28 +++++++++---------- .../Features/Fusion/FlowQuery.feature | 4 +-- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php index 4a036814568..337cc62f10b 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php @@ -37,7 +37,7 @@ class ExpressionBasedNodeLabelGenerator implements NodeLabelGeneratorInterface * @var string */ protected $expression = <<<'EEL' - ${(node.nodeType.label ? node.nodeType.label : node.nodeType.name) + (node.nodeName ? ' (' + node.nodeName.value + ')' : '')} + ${(node.nodeType.label ? node.nodeType.label : node.nodeTypeName.value) + (node.nodeName ? ' (' + node.nodeName.value + ')' : '')} EEL; /** diff --git a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php index dcd9efeb4b6..9d6c59e363b 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php @@ -9,7 +9,7 @@ use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Flow\ObjectManagement\ObjectManagerInterface; -use Neos\Neos\Utility\NodeTypeWithFallbackProvider; +use Neos\Neos\Domain\Service\NodeTypeNameFactory; /** * @internal please reference the interface {@see NodeLabelRendererInterface} instead. @@ -17,8 +17,6 @@ #[Flow\Scope('singleton')] final readonly class NodeLabelRenderer implements NodeLabelRendererInterface { - use NodeTypeWithFallbackProvider; - public function __construct( private ContentRepositoryRegistry $contentRepositoryRegistry, private ObjectManagerInterface $objectManager @@ -27,14 +25,16 @@ public function __construct( public function renderNodeLabel(Node $node): NodeLabel { - $nodeType = $this->getNodeType($node); + $nodeTypeManager = $this->contentRepositoryRegistry->get($node->subgraphIdentity->contentRepositoryId)->getNodeTypeManager(); + $nodeType = $nodeTypeManager->getNodeType($node->nodeTypeName) + ?? $nodeTypeManager->getNodeType(NodeTypeNameFactory::forFallback()); $generator = $this->getNodeLabelGeneratorForNodeType($nodeType); return $generator->getLabel($node); } - private function getNodeLabelGeneratorForNodeType(NodeType $nodeType): NodeLabelGeneratorInterface + private function getNodeLabelGeneratorForNodeType(?NodeType $nodeType): NodeLabelGeneratorInterface { - if ($nodeType->hasConfiguration('label.generatorClass')) { + if ($nodeType?->hasConfiguration('label.generatorClass')) { $nodeLabelGeneratorClassName = $nodeType->getConfiguration('label.generatorClass'); $nodeLabelGenerator = $this->objectManager->get($nodeLabelGeneratorClassName); if (!$nodeLabelGenerator instanceof NodeLabelGeneratorInterface) { @@ -44,7 +44,7 @@ private function getNodeLabelGeneratorForNodeType(NodeType $nodeType): NodeLabel 1682950942 ); } - } elseif ($nodeType->hasConfiguration('label') && is_string($nodeType->getConfiguration('label'))) { + } elseif ($nodeType?->hasConfiguration('label') && is_string($nodeType->getConfiguration('label'))) { /** @var ExpressionBasedNodeLabelGenerator $nodeLabelGenerator */ $nodeLabelGenerator = $this->objectManager->get(ExpressionBasedNodeLabelGenerator::class); $nodeLabelGenerator->setExpression($nodeType->getConfiguration('label')); diff --git a/Neos.Neos/Configuration/Settings.yaml b/Neos.Neos/Configuration/Settings.yaml index 0561ad4c399..6259169a832 100755 --- a/Neos.Neos/Configuration/Settings.yaml +++ b/Neos.Neos/Configuration/Settings.yaml @@ -390,6 +390,20 @@ Neos: ö: oe ü: ue + # Configures defaults for node label generation + labelGenerator: + eel: + defaultContext: + String: Neos\Eel\Helper\StringHelper + Array: Neos\Eel\Helper\ArrayHelper + Date: Neos\Eel\Helper\DateHelper + Configuration: Neos\Eel\Helper\ConfigurationHelper + Math: Neos\Eel\Helper\MathHelper + Json: Neos\Eel\Helper\JsonHelper + I18n: Neos\Flow\I18n\EelHelper\TranslationHelper + q: Neos\Eel\FlowQuery\FlowQuery::q + Neos.Node: Neos\Neos\Fusion\Helper\NodeHelper + Flow: aop: globalObjects: @@ -471,20 +485,6 @@ Neos: neos-site: Sites neos-plugin: Plugins - # Configures defaults for node label generation - labelGenerator: - eel: - defaultContext: - String: Neos\Eel\Helper\StringHelper - Array: Neos\Eel\Helper\ArrayHelper - Date: Neos\Eel\Helper\DateHelper - Configuration: Neos\Eel\Helper\ConfigurationHelper - Math: Neos\Eel\Helper\MathHelper - Json: Neos\Eel\Helper\JsonHelper - I18n: Neos\Flow\I18n\EelHelper\TranslationHelper - q: Neos\Eel\FlowQuery\FlowQuery::q - Neos.Node: Neos\Neos\Fusion\Helper\NodeHelper - Fusion: rendering: exceptionHandler: Neos\Fusion\Core\ExceptionHandlers\ThrowingHandler diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature index f8bbbfc3a90..01bd3da60a6 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/FlowQuery.feature @@ -413,7 +413,7 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. { "property": "Node a1", "identifier": "a1", - "label": "Neos.Neos:Test.DocumentType1", + "label": "Neos.Neos:Test.DocumentType1 (a1)", "nodeTypeName": "Neos.Neos:Test.DocumentType1" } """ @@ -436,7 +436,7 @@ Feature: Tests for the "Neos.ContentRepository" Flow Query methods. { "property": "Node a1", "identifier": "a1", - "label": "Neos.Neos:Test.DocumentType1", + "label": "Neos.Neos:Test.DocumentType1 (a1)", "nodeTypeName": "Neos.Neos:Test.DocumentType1" } """ From 296535b5ae0f89b5f224c8cb244e6d80607cdb0d Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 18 Mar 2024 23:58:40 +0100 Subject: [PATCH 188/214] TASK: Deprecate `NodeAddress` The nodeadress was added 6 years ago without the concept of multiple crs. Its usages will be replaced by the new node attached node address --- Neos.Neos/Classes/FrontendRouting/NodeAddress.php | 4 +++- Neos.Neos/Classes/FrontendRouting/NodeAddressFactory.php | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Neos.Neos/Classes/FrontendRouting/NodeAddress.php b/Neos.Neos/Classes/FrontendRouting/NodeAddress.php index 83db25d52c6..1126c00babe 100644 --- a/Neos.Neos/Classes/FrontendRouting/NodeAddress.php +++ b/Neos.Neos/Classes/FrontendRouting/NodeAddress.php @@ -31,7 +31,9 @@ * * It is used in Neos Routing to build a URI to a node. * - * @api + * @deprecated will be removed before Final 9.0 + * The NodeAddress was added 6 years ago without the concept of multiple crs + * Its usages will be replaced by the new node attached node address */ #[Flow\Proxy(false)] final readonly class NodeAddress diff --git a/Neos.Neos/Classes/FrontendRouting/NodeAddressFactory.php b/Neos.Neos/Classes/FrontendRouting/NodeAddressFactory.php index 3a2f28c2a7f..9781840a45a 100644 --- a/Neos.Neos/Classes/FrontendRouting/NodeAddressFactory.php +++ b/Neos.Neos/Classes/FrontendRouting/NodeAddressFactory.php @@ -22,7 +22,7 @@ use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** - * @api + * @deprecated will be removed before Final 9.0 */ class NodeAddressFactory { From bdd41a218c827eb3ddbe099201b1b66c456cc0ec Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 18 Mar 2024 23:59:56 +0100 Subject: [PATCH 189/214] FEATURE: The ~bourne~ node identity This adds a value object to the core encapsulating all identifiers to identify/fetch a single node. --- .../ContentGraph/VisibilityConstraints.php | 4 + .../Classes/SharedModel/Node/NodeAddress.php | 90 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/VisibilityConstraints.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/VisibilityConstraints.php index b3d6f466c7e..7462e28a4e1 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/VisibilityConstraints.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/VisibilityConstraints.php @@ -39,6 +39,10 @@ public function getHash(): string return md5(implode('|', $this->tagConstraints->toStringArray())); } + /** + * A non restricted subgraph can find all nodes without filtering. + * Disabled nodes are this way also findable. + */ public static function withoutRestrictions(): self { return new self(SubtreeTags::createEmpty()); diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php b/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php new file mode 100644 index 00000000000..2c74d0e8210 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php @@ -0,0 +1,90 @@ +getContentGraph($nodeAddress->workspaceName)->getSubgraph( + * $nodeAddress->dimensionSpacePoint, + * VisibilityConstraints::withoutRestrictions() + * ); + * $node = $subgraph->findNodeById($nodeAddress->nodeAggregateId); + * + * @api + */ +final readonly class NodeAddress implements \JsonSerializable +{ + private function __construct( + public ContentRepositoryId $contentRepositoryId, + public WorkspaceName $workspaceName, + public DimensionSpacePoint $dimensionSpacePoint, + public NodeAggregateId $nodeAggregateId, + ) { + } + + public static function create( + ContentRepositoryId $contentRepositoryId, + WorkspaceName $workspaceName, + DimensionSpacePoint $dimensionSpacePoint, + NodeAggregateId $nodeAggregateId, + ): self { + return new self($contentRepositoryId, $workspaceName, $dimensionSpacePoint, $nodeAggregateId); + } + + /** + * @param array $array + */ + public static function fromArray(array $array): self + { + return new self( + ContentRepositoryId::fromString($array['contentRepositoryId']), + WorkspaceName::fromString($array['workspaceName']), + DimensionSpacePoint::fromArray($array['dimensionSpacePoint']), + NodeAggregateId::fromString($array['nodeAggregateId']) + ); + } + + public static function fromJsonString(string $jsonString): self + { + return self::fromArray(\json_decode($jsonString, true, JSON_THROW_ON_ERROR)); + } + + public function withNodeAggregateId(NodeAggregateId $nodeAggregateId): self + { + return new self($this->contentRepositoryId, $this->workspaceName, $this->dimensionSpacePoint, $nodeAggregateId); + } + + public function equals(self $other): bool + { + return $this->contentRepositoryId->equals($other->contentRepositoryId) + && $this->workspaceName->equals($other->workspaceName) + && $this->dimensionSpacePoint->equals($other->dimensionSpacePoint) + && $this->nodeAggregateId->equals($other->nodeAggregateId); + } + + public function toJson(): string + { + return json_encode($this, JSON_THROW_ON_ERROR); + } + + public function jsonSerialize(): mixed + { + return [ + 'contentRepositoryId' => $this->contentRepositoryId, + 'workspaceName' => $this->workspaceName, + 'dimensionSpacePoint' => $this->dimensionSpacePoint, + 'nodeAggregateId' => $this->nodeAggregateId + ]; + } +} From c9c054a8125405f6c498ca8d8da7c6cc4a8cc6f1 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 19 Mar 2024 21:37:41 +0100 Subject: [PATCH 190/214] TASK: Build nodes with node address in node factory --- .../src/Domain/Repository/ContentGraph.php | 3 ++ .../src/Domain/Repository/ContentSubgraph.php | 30 ++++++++++--- .../src/Domain/Repository/NodeFactory.php | 43 +++++++++++++++---- .../src/Domain/Repository/NodeFactory.php | 11 ++++- .../Classes/Projection/ContentGraph/Node.php | 10 ++++- .../Projection/ContentGraph/NodeAggregate.php | 1 + .../Classes/Unit/NodeSubjectProvider.php | 10 ++++- 7 files changed, 90 insertions(+), 18 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index d21056908c7..8039546abaf 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -171,6 +171,7 @@ public function findNodeAggregateById( return $this->nodeFactory->mapNodeRowsToNodeAggregate( $this->fetchRows($queryBuilder), + $this->workspaceName, $this->contentStreamId, VisibilityConstraints::withoutRestrictions() ); @@ -236,6 +237,7 @@ public function findParentNodeAggregateByChildOriginDimensionSpacePoint(NodeAggr return $this->nodeFactory->mapNodeRowsToNodeAggregate( $this->fetchRows($queryBuilder), + $this->workspaceName, $this->contentStreamId, VisibilityConstraints::withoutRestrictions() ); @@ -338,6 +340,7 @@ private function mapQueryBuilderToNodeAggregates(QueryBuilder $queryBuilder): it { return $this->nodeFactory->mapNodeRowsToNodeAggregates( $this->fetchRows($queryBuilder), + $this->workspaceName, $this->contentStreamId, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index 420b8d2a80f..3e003e51bbb 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -92,7 +92,6 @@ final class ContentSubgraph implements ContentSubgraphInterface public function __construct( private readonly ContentRepositoryId $contentRepositoryId, - /** @phpstan-ignore-next-line */ private readonly WorkspaceName $workspaceName, private readonly ContentStreamId $contentStreamId, private readonly DimensionSpacePoint $dimensionSpacePoint, @@ -290,7 +289,13 @@ public function findSubtree(NodeAggregateId $entryNodeAggregateId, FindSubtreeFi foreach (array_reverse($result) as $nodeData) { $nodeAggregateId = $nodeData['nodeaggregateid']; $parentNodeAggregateId = $nodeData['parentNodeAggregateId']; - $node = $this->nodeFactory->mapNodeRowToNode($nodeData, $this->contentStreamId, $this->dimensionSpacePoint, $this->visibilityConstraints); + $node = $this->nodeFactory->mapNodeRowToNode( + $nodeData, + $this->workspaceName, + $this->contentStreamId, + $this->dimensionSpacePoint, + $this->visibilityConstraints + ); $subtree = new Subtree((int)$nodeData['level'], $node, array_key_exists($nodeAggregateId, $subtreesByParentNodeId) ? array_reverse($subtreesByParentNodeId[$nodeAggregateId]) : []); if ($subtree->level === 0) { return $subtree; @@ -319,6 +324,7 @@ public function findAncestorNodes(NodeAggregateId $entryNodeAggregateId, FindAnc return $this->nodeFactory->mapNodeRowsToNodes( $nodeRows, + $this->workspaceName, $this->contentStreamId, $this->dimensionSpacePoint, $this->visibilityConstraints @@ -374,6 +380,7 @@ public function findClosestNode(NodeAggregateId $entryNodeAggregateId, FindClose ); return $this->nodeFactory->mapNodeRowsToNodes( $nodeRows, + $this->workspaceName, $this->contentStreamId, $this->dimensionSpacePoint, $this->visibilityConstraints @@ -391,7 +398,13 @@ public function findDescendantNodes(NodeAggregateId $entryNodeAggregateId, FindD } $queryBuilderCte->addOrderBy('level')->addOrderBy('position'); $nodeRows = $this->fetchCteResults($queryBuilderInitial, $queryBuilderRecursive, $queryBuilderCte, 'tree'); - return $this->nodeFactory->mapNodeRowsToNodes($nodeRows, $this->contentStreamId, $this->dimensionSpacePoint, $this->visibilityConstraints); + return $this->nodeFactory->mapNodeRowsToNodes( + $nodeRows, + $this->workspaceName, + $this->contentStreamId, + $this->dimensionSpacePoint, + $this->visibilityConstraints + ); } public function countDescendantNodes(NodeAggregateId $entryNodeAggregateId, CountDescendantNodesFilter $filter): int @@ -668,6 +681,7 @@ private function fetchNode(QueryBuilder $queryBuilder): ?Node } return $this->nodeFactory->mapNodeRowToNode( $nodeRow, + $this->workspaceName, $this->contentStreamId, $this->dimensionSpacePoint, $this->visibilityConstraints @@ -681,7 +695,13 @@ private function fetchNodes(QueryBuilder $queryBuilder): Nodes } catch (DbalDriverException | DbalException $e) { throw new \RuntimeException(sprintf('Failed to fetch nodes: %s', $e->getMessage()), 1678292896, $e); } - return $this->nodeFactory->mapNodeRowsToNodes($nodeRows, $this->contentStreamId, $this->dimensionSpacePoint, $this->visibilityConstraints); + return $this->nodeFactory->mapNodeRowsToNodes( + $nodeRows, + $this->workspaceName, + $this->contentStreamId, + $this->dimensionSpacePoint, + $this->visibilityConstraints + ); } private function fetchCount(QueryBuilder $queryBuilder): int @@ -700,7 +720,7 @@ private function fetchReferences(QueryBuilder $queryBuilder): References } catch (DbalDriverException | DbalException $e) { throw new \RuntimeException(sprintf('Failed to fetch references: %s', $e->getMessage()), 1678364944, $e); } - return $this->nodeFactory->mapReferenceRowsToReferences($referenceRows, $this->contentStreamId, $this->dimensionSpacePoint, $this->visibilityConstraints); + return $this->nodeFactory->mapReferenceRowsToReferences($referenceRows, $this->workspaceName, $this->contentStreamId, $this->dimensionSpacePoint, $this->visibilityConstraints); } /** diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php index 88887b67d9a..a0f550ba75a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php @@ -16,7 +16,6 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet; -use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePointSet; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\SerializedPropertyValues; use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTags; @@ -38,11 +37,13 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Node\ReferenceName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * Implementation detail of ContentGraph and ContentSubgraph @@ -64,6 +65,7 @@ public function __construct( */ public function mapNodeRowToNode( array $nodeRow, + WorkspaceName $workspaceName, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints @@ -73,13 +75,19 @@ public function mapNodeRowToNode( : null; return Node::create( + NodeAddress::create( + $this->contentRepositoryId, + $workspaceName, + $dimensionSpacePoint, + $nodeId = NodeAggregateId::fromString($nodeRow['nodeaggregateid']) + ), ContentSubgraphIdentity::create( $this->contentRepositoryId, $contentStreamId, $dimensionSpacePoint, $visibilityConstraints ), - NodeAggregateId::fromString($nodeRow['nodeaggregateid']), + $nodeId, $this->dimensionSpacePointRepository->getOriginDimensionSpacePointByHash($nodeRow['origindimensionspacepointhash']), NodeAggregateClassification::from($nodeRow['classification']), NodeTypeName::fromString($nodeRow['nodetypename']), @@ -99,10 +107,21 @@ public function mapNodeRowToNode( /** * @param array> $nodeRows */ - public function mapNodeRowsToNodes(array $nodeRows, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints): Nodes - { + public function mapNodeRowsToNodes( + array $nodeRows, + WorkspaceName $workspaceName, + ContentStreamId $contentStreamId, + DimensionSpacePoint $dimensionSpacePoint, + VisibilityConstraints $visibilityConstraints + ): Nodes { return Nodes::fromArray( - array_map(fn (array $nodeRow) => $this->mapNodeRowToNode($nodeRow, $contentStreamId, $dimensionSpacePoint, $visibilityConstraints), $nodeRows) + array_map(fn (array $nodeRow) => $this->mapNodeRowToNode( + $nodeRow, + $workspaceName, + $contentStreamId, + $dimensionSpacePoint, + $visibilityConstraints + ), $nodeRows) ); } @@ -119,6 +138,7 @@ public function createPropertyCollectionFromJsonString(string $jsonString): Prop */ public function mapReferenceRowsToReferences( array $nodeRows, + WorkspaceName $workspaceName, ContentStreamId $contentStreamId, DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints @@ -127,6 +147,7 @@ public function mapReferenceRowsToReferences( foreach ($nodeRows as $nodeRow) { $node = $this->mapNodeRowToNode( $nodeRow, + $workspaceName, $contentStreamId, $dimensionSpacePoint, $visibilityConstraints @@ -149,6 +170,7 @@ public function mapReferenceRowsToReferences( */ public function mapNodeRowsToNodeAggregate( array $nodeRows, + WorkspaceName $workspaceName, ContentStreamId $contentStreamId, VisibilityConstraints $visibilityConstraints ): ?NodeAggregate { @@ -175,6 +197,7 @@ public function mapNodeRowsToNodeAggregate( // ... so we handle occupation exactly once ... $nodesByOccupiedDimensionSpacePoints[$occupiedDimensionSpacePoint->hash] = $this->mapNodeRowToNode( $nodeRow, + $workspaceName, $contentStreamId, $occupiedDimensionSpacePoint->toDimensionSpacePoint(), $visibilityConstraints @@ -230,6 +253,7 @@ public function mapNodeRowsToNodeAggregate( */ public function mapNodeRowsToNodeAggregates( iterable $nodeRows, + WorkspaceName $workspaceName, ContentStreamId $contentStreamId, VisibilityConstraints $visibilityConstraints ): iterable { @@ -255,10 +279,11 @@ public function mapNodeRowsToNodeAggregates( // ... so we handle occupation exactly once ... $nodesByOccupiedDimensionSpacePointsByNodeAggregate [$rawNodeAggregateId][$occupiedDimensionSpacePoint->hash] = $this->mapNodeRowToNode( - $nodeRow, - $contentStreamId, - $occupiedDimensionSpacePoint->toDimensionSpacePoint(), - $visibilityConstraints + $nodeRow, + $workspaceName, + $contentStreamId, + $occupiedDimensionSpacePoint->toDimensionSpacePoint(), + $visibilityConstraints ); $occupiedDimensionSpacePointsByNodeAggregate[$rawNodeAggregateId][] = $occupiedDimensionSpacePoint; diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php index da9b96cf28c..a0ea99cb7c4 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php @@ -37,11 +37,13 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Timestamps; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Node\ReferenceName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * The node factory for mapping database rows to nodes and node aggregates @@ -77,13 +79,20 @@ public function mapNodeRowToNode( : null; return Node::create( + NodeAddress::create( + $this->contentRepositoryId, + // todo use actual workspace name + WorkspaceName::fromString('missing'), + $dimensionSpacePoint ?: DimensionSpacePoint::fromJsonString($nodeRow['dimensionspacepoint']), + $nodeId = NodeAggregateId::fromString($nodeRow['nodeaggregateid']) + ), ContentSubgraphIdentity::create( $this->contentRepositoryId, $contentStreamId ?: ContentStreamId::fromString($nodeRow['contentstreamid']), $dimensionSpacePoint ?: DimensionSpacePoint::fromJsonString($nodeRow['dimensionspacepoint']), $visibilityConstraints ), - NodeAggregateId::fromString($nodeRow['nodeaggregateid']), + $nodeId, OriginDimensionSpacePoint::fromJsonString($nodeRow['origindimensionspacepoint']), NodeAggregateClassification::from($nodeRow['classification']), NodeTypeName::fromString($nodeRow['nodetypename']), diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index 48250a8107c..5276789f1b8 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -17,6 +17,7 @@ use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; @@ -34,11 +35,14 @@ * call findChildNodes() {@see ContentSubgraphInterface::findChildNodes()} * on the subgraph. * + * The identity of a node is summarized here {@see NodeAddress} + * * @api Note: The constructor is not part of the public API */ final readonly class Node { /** + * @param NodeAddress $address The node's "Read Model" identity. * @param ContentSubgraphIdentity $subgraphIdentity This is part of the node's "Read Model" identity which is defined by: {@see self::subgraphIdentity} and {@see self::nodeAggregateId}. With this information, you can fetch a Subgraph using {@see ContentGraphInterface::getSubgraph()}. * @param NodeAggregateId $nodeAggregateId NodeAggregateId (identifier) of this node. This is part of the node's "Read Model" identity which is defined by: {@see self::subgraphIdentity} and {@see self::nodeAggregateId} * @param OriginDimensionSpacePoint $originDimensionSpacePoint The DimensionSpacePoint the node originates in. Usually needed to address a Node in a NodeAggregate in order to update it. @@ -51,6 +55,8 @@ * @param Timestamps $timestamps Creation and modification timestamps of this node */ private function __construct( + public NodeAddress $address, + /** @deprecated will be removed before the final 9.0 release */ public ContentSubgraphIdentity $subgraphIdentity, public NodeAggregateId $nodeAggregateId, public OriginDimensionSpacePoint $originDimensionSpacePoint, @@ -70,9 +76,9 @@ private function __construct( /** * @internal The signature of this method can change in the future! */ - public static function create(ContentSubgraphIdentity $subgraphIdentity, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $originDimensionSpacePoint, NodeAggregateClassification $classification, NodeTypeName $nodeTypeName, ?NodeType $nodeType, PropertyCollection $properties, ?NodeName $nodeName, NodeTags $tags, Timestamps $timestamps): self + public static function create(NodeAddress $address, ContentSubgraphIdentity $subgraphIdentity, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $originDimensionSpacePoint, NodeAggregateClassification $classification, NodeTypeName $nodeTypeName, ?NodeType $nodeType, PropertyCollection $properties, ?NodeName $nodeName, NodeTags $tags, Timestamps $timestamps): self { - return new self($subgraphIdentity, $nodeAggregateId, $originDimensionSpacePoint, $classification, $nodeTypeName, $nodeType, $properties, $nodeName, $tags, $timestamps); + return new self($address, $subgraphIdentity, $nodeAggregateId, $originDimensionSpacePoint, $classification, $nodeTypeName, $nodeType, $properties, $nodeName, $tags, $timestamps); } /** diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeAggregate.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeAggregate.php index 15fa8537905..d75692f9369 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeAggregate.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/NodeAggregate.php @@ -65,6 +65,7 @@ * @param OriginByCoverage $occupationByCovered * @param DimensionSpacePointsBySubtreeTags $dimensionSpacePointsBySubtreeTags dimension space points for every subtree tag this aggregate is *explicitly* tagged with (excluding inherited tags) */ + // todo add workspace name and content repository id and remove cs id public function __construct( public ContentStreamId $contentStreamId, public NodeAggregateId $nodeAggregateId, diff --git a/Neos.ContentRepository.TestSuite/Classes/Unit/NodeSubjectProvider.php b/Neos.ContentRepository.TestSuite/Classes/Unit/NodeSubjectProvider.php index 61ce76d10d0..705edad9aa9 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Unit/NodeSubjectProvider.php +++ b/Neos.ContentRepository.TestSuite/Classes/Unit/NodeSubjectProvider.php @@ -35,10 +35,12 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Timestamps; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Serializer; @@ -87,13 +89,19 @@ public function createMinimalNodeOfType( ): Node { $serializedDefaultPropertyValues = SerializedPropertyValues::defaultFromNodeType($nodeType, $this->propertyConverter); return Node::create( + NodeAddress::create( + ContentRepositoryId::fromString('default'), + WorkspaceName::forLive(), + DimensionSpacePoint::createWithoutDimensions(), + $id = NodeAggregateId::create() + ), ContentSubgraphIdentity::create( ContentRepositoryId::fromString('default'), ContentStreamId::fromString('cs-id'), DimensionSpacePoint::createWithoutDimensions(), VisibilityConstraints::withoutRestrictions() ), - NodeAggregateId::create(), + $id, OriginDimensionSpacePoint::createWithoutDimensions(), NodeAggregateClassification::CLASSIFICATION_REGULAR, $nodeType->name, From a1168cf733b050fb4f402571f84fc867ee6cbf21 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 19 Mar 2024 23:02:33 +0100 Subject: [PATCH 191/214] TASK: Adjust to use Node's `WorkspaceName` instead of `ContentStreamId` --- .../src/Domain/Repository/ContentGraph.php | 2 +- .../Command/ContentCommandController.php | 25 ++++++++--------- .../Classes/ContentRepositoryRegistry.php | 9 ++----- .../Management/WorkspacesController.php | 4 +-- .../Controller/Service/NodesController.php | 27 +++++-------------- .../Domain/Service/SiteNodeUtility.php | 7 +---- .../Cache/NeosFusionContextSerializer.php | 19 ++++++------- .../DimensionsMenuItemsImplementation.php | 8 +++--- .../Classes/Fusion/Helper/DimensionHelper.php | 22 +++++++-------- .../Classes/View/FusionExceptionView.php | 18 ++++++------- .../Service/TimeableNodeVisibilityService.php | 15 ++++------- 11 files changed, 65 insertions(+), 91 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 8039546abaf..326f5e52f9c 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -306,7 +306,7 @@ public function countNodes(): int try { return (int)$result->fetchOne(); } catch (DriverException | DBALException $e) { - throw new \RuntimeException(sprintf('Failed to fetch rows from database: %s', $e->getMessage()), 1701444590, $e); + throw new \RuntimeException(sprintf('Failed to count rows in database: %s', $e->getMessage()), 1701444590, $e); } } diff --git a/Neos.ContentRepositoryRegistry/Classes/Command/ContentCommandController.php b/Neos.ContentRepositoryRegistry/Classes/Command/ContentCommandController.php index 87e8e0e936d..aebcfb0dd96 100644 --- a/Neos.ContentRepositoryRegistry/Classes/Command/ContentCommandController.php +++ b/Neos.ContentRepositoryRegistry/Classes/Command/ContentCommandController.php @@ -24,6 +24,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindRootNodeAggregatesFilter; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; @@ -141,23 +142,23 @@ public function createVariantsRecursivelyCommand(string $source, string $target, $contentRepositoryId = ContentRepositoryId::fromString($contentRepository); $sourceSpacePoint = DimensionSpacePoint::fromJsonString($source); $targetSpacePoint = OriginDimensionSpacePoint::fromJsonString($target); + $workspaceName = WorkspaceName::fromString($workspace); $contentRepositoryInstance = $this->contentRepositoryRegistry->get($contentRepositoryId); - $workspaceInstance = $contentRepositoryInstance->getWorkspaceFinder()->findOneByName(WorkspaceName::fromString($workspace)); - if ($workspaceInstance === null) { - $this->outputLine('Workspace "%s" does not exist', [$workspace]); + + try { + $sourceSubgraph = $contentRepositoryInstance->getContentGraph($workspaceName)->getSubgraph( + $sourceSpacePoint, + VisibilityConstraints::withoutRestrictions() + ); + } catch (WorkspaceDoesNotExist) { + $this->outputLine('Workspace "%s" does not exist', [$workspaceName->value]); $this->quit(1); } - $this->outputLine('Creating %s to %s in workspace %s (content repository %s)', [$sourceSpacePoint->toJson(), $targetSpacePoint->toJson(), $workspaceInstance->workspaceName->value, $contentRepositoryId->value]); - $this->outputLine('Resolved content stream %s', [$workspaceInstance->currentContentStreamId->value]); - - $sourceSubgraph = $contentRepositoryInstance->getContentGraph(WorkspaceName::fromString($workspace))->getSubgraph( - $sourceSpacePoint, - VisibilityConstraints::withoutRestrictions() - ); + $this->outputLine('Creating %s to %s in workspace %s (content repository %s)', [$sourceSpacePoint->toJson(), $targetSpacePoint->toJson(), $workspaceName->value, $contentRepositoryId->value]); - $rootNodeAggregates = $contentRepositoryInstance->getContentGraph($workspaceInstance->workspaceName) + $rootNodeAggregates = $contentRepositoryInstance->getContentGraph($workspaceName) ->findRootNodeAggregates(FindRootNodeAggregatesFilter::create()); @@ -168,7 +169,7 @@ public function createVariantsRecursivelyCommand(string $source, string $target, $rootNodeAggregate->nodeAggregateId, $sourceSubgraph, $targetSpacePoint, - $workspaceInstance->workspaceName, + $workspaceName, $contentRepositoryInstance, ) ); diff --git a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php index a35269902f7..412abb37eb7 100644 --- a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php +++ b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php @@ -15,7 +15,6 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ProjectionCatchUpTriggerInterface; use Neos\ContentRepository\Core\Projection\ProjectionFactoryInterface; -use Neos\ContentRepository\Core\Projection\Workspace\Workspace; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\User\UserIdProviderInterface; use Neos\ContentRepositoryRegistry\Exception\ContentRepositoryNotFoundException; @@ -93,13 +92,9 @@ public function resetFactoryInstance(ContentRepositoryId $contentRepositoryId): public function subgraphForNode(Node $node): ContentSubgraphInterface { - $contentRepository = $this->get($node->subgraphIdentity->contentRepositoryId); + $contentRepository = $this->get($node->address->contentRepositoryId); - // FIXME: node->workspace - /** @var Workspace $workspace */ - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($node->subgraphIdentity->contentStreamId); - - return $contentRepository->getContentGraph($workspace->workspaceName)->getSubgraph( + return $contentRepository->getContentGraph($node->address->workspaceName)->getSubgraph( $node->subgraphIdentity->dimensionSpacePoint, $node->subgraphIdentity->visibilityConstraints ); diff --git a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php index c603cd82dcf..c1c953efd24 100644 --- a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php +++ b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php @@ -870,10 +870,10 @@ protected function computeSiteChanges(Workspace $selectedWorkspace, ContentRepos */ protected function getOriginalNode( Node $modifiedNode, - WorkspaceName $workspaceName, + WorkspaceName $baseWorkspaceName, ContentRepository $contentRepository, ): ?Node { - $baseSubgraph = $contentRepository->getContentGraph($workspaceName)->getSubgraph( + $baseSubgraph = $contentRepository->getContentGraph($baseWorkspaceName)->getSubgraph( $modifiedNode->subgraphIdentity->dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() ); diff --git a/Neos.Neos/Classes/Controller/Service/NodesController.php b/Neos.Neos/Classes/Controller/Service/NodesController.php index bff9d28db9d..c2905fb04a5 100644 --- a/Neos.Neos/Classes/Controller/Service/NodesController.php +++ b/Neos.Neos/Classes/Controller/Service/NodesController.php @@ -131,15 +131,6 @@ public function indexAction( unset($contextNode); if (is_null($nodeAddress)) { - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName( - WorkspaceName::fromString($workspaceName) - ); - if (is_null($workspace)) { - throw new \InvalidArgumentException( - 'Could not resolve a node address for the given parameters.', - 1645631728 - ); - } $subgraph = $contentRepository->getContentGraph(WorkspaceName::fromString($workspaceName))->getSubgraph( DimensionSpacePoint::fromLegacyDimensionArray($dimensions), VisibilityConstraints::withoutRestrictions() // we are in a backend controller. @@ -204,12 +195,10 @@ public function showAction(string $identifier, string $workspaceName = 'live', a ->contentRepositoryId; $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $workspace = $contentRepository->getWorkspaceFinder() - ->findOneByName(WorkspaceName::fromString($workspaceName)); - assert($workspace instanceof Workspace); + $workspaceName = WorkspaceName::fromString($workspaceName); $dimensionSpacePoint = DimensionSpacePoint::fromLegacyDimensionArray($dimensions); - $subgraph = $contentRepository->getContentGraph(WorkspaceName::fromString($workspaceName)) + $subgraph = $contentRepository->getContentGraph($workspaceName) ->getSubgraph( $dimensionSpacePoint, VisibilityConstraints::withoutRestrictions() @@ -220,7 +209,7 @@ public function showAction(string $identifier, string $workspaceName = 'live', a if ($node === null) { $this->addExistingNodeVariantInformationToResponse( $nodeAggregateId, - $workspace->workspaceName, + $workspaceName, $dimensionSpacePoint, $contentRepository ); @@ -273,11 +262,9 @@ public function createAction( ->contentRepositoryId; $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $workspace = $contentRepository->getWorkspaceFinder() - ->findOneByName(WorkspaceName::fromString($workspaceName)); - assert($workspace instanceof Workspace); + $workspaceName = WorkspaceName::fromString($workspaceName); - $contentGraph = $contentRepository->getContentGraph(WorkspaceName::fromString($workspaceName)); + $contentGraph = $contentRepository->getContentGraph($workspaceName); $sourceSubgraph = $contentGraph ->getSubgraph( DimensionSpacePoint::fromLegacyDimensionArray($sourceDimensions), @@ -294,7 +281,7 @@ public function createAction( if ($mode === 'adoptFromAnotherDimension' || $mode === 'adoptFromAnotherDimensionAndCopyContent') { CatchUpTriggerWithSynchronousOption::synchronously(fn() => $this->adoptNodeAndParents( - $workspace->workspaceName, + $workspaceName, $nodeAggregateId, $sourceSubgraph, $targetSubgraph, @@ -305,7 +292,7 @@ public function createAction( $this->redirect('show', null, null, [ 'identifier' => $nodeAggregateId->value, - 'workspaceName' => $workspaceName, + 'workspaceName' => $workspaceName->value, 'dimensions' => $dimensions ]); } else { diff --git a/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php b/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php index 34614a86683..fb7829123bf 100644 --- a/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php +++ b/Neos.Neos/Classes/Domain/Service/SiteNodeUtility.php @@ -18,7 +18,6 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; @@ -42,13 +41,9 @@ public function __construct( * To find the site node for the live workspace in a 0 dimensional content repository use: * * ```php - * $contentRepository = $this->contentRepositoryRegistry->get($site->getConfiguration()->contentRepositoryId); - * $liveWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive()) - * ?? throw new \RuntimeException('Expected live workspace to exist.'); - * * $siteNode = $this->siteNodeUtility->findSiteNodeBySite( * $site, - * $liveWorkspace->currentContentStreamId, + * WorkspaceName::forLive(), * DimensionSpacePoint::createWithoutDimensions(), * VisibilityConstraints::frontend() * ); diff --git a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php index 462aad66e6c..ee700b86748 100644 --- a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php +++ b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php @@ -8,6 +8,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; @@ -73,21 +74,21 @@ private function tryDeserializeNode(array $serializedNode): ?Node $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::fromString($serializedNode['workspaceName'])); - if (!$workspace) { + try { + $workspaceName = WorkspaceName::fromString($serializedNode['workspaceName']); + $subgraph = $contentRepository->getContentGraph($workspaceName)->getSubgraph( + DimensionSpacePoint::fromArray($serializedNode['dimensionSpacePoint']), + $workspaceName->isLive() + ? VisibilityConstraints::frontend() + : VisibilityConstraints::withoutRestrictions() + ); + } catch (WorkspaceDoesNotExist $exception) { // in case the workspace was deleted the rendering should probably not come to this very point // still if it does we fail silently // this is also the behaviour for when the property mapper is used return null; } - $subgraph = $contentRepository->getContentGraph($workspace->workspaceName)->getSubgraph( - DimensionSpacePoint::fromArray($serializedNode['dimensionSpacePoint']), - $workspace->isPublicWorkspace() - ? VisibilityConstraints::frontend() - : VisibilityConstraints::withoutRestrictions() - ); - $node = $subgraph->findNodeById(NodeAggregateId::fromString($serializedNode['nodeAggregateId'])); if (!$node) { // instead of crashing the whole rendering, by silently returning null we will most likely just break diff --git a/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php index 42b514c6f8a..7c1c3e77346 100644 --- a/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php @@ -9,6 +9,7 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; /** @@ -59,12 +60,11 @@ protected function buildItems(): array $interDimensionalVariationGraph = $dimensionMenuItemsImplementationInternals->interDimensionalVariationGraph; $currentDimensionSpacePoint = $currentNode->subgraphIdentity->dimensionSpacePoint; $contentDimensionIdentifierToLimitTo = $this->getContentDimensionIdentifierToLimitTo(); - // FIXME: node->workspaceName - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($currentNode->subgraphIdentity->contentStreamId); - if (is_null($workspace)) { + try { + $contentGraph = $contentRepository->getContentGraph($currentNode->address->workspaceName); + } catch (WorkspaceDoesNotExist) { return $menuItems; } - $contentGraph = $contentRepository->getContentGraph($workspace->workspaceName); foreach ($interDimensionalVariationGraph->getDimensionSpacePoints() as $dimensionSpacePoint) { $variant = null; diff --git a/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php b/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php index ddcf6d6b269..5653035ed9c 100644 --- a/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php @@ -20,6 +20,7 @@ use Neos\ContentRepository\Core\Dimension\ContentDimensionValues; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Eel\ProtectedContextAwareInterface; use Neos\Flow\Annotations as Flow; @@ -142,19 +143,18 @@ public function findVariantInDimension(Node $node, ContentDimensionId|string $di { $contentDimensionId = is_string($dimensionName) ? new ContentDimensionId($dimensionName) : $dimensionName; $contentDimensionValue = is_string($dimensionValue) ? new ContentDimensionValue($dimensionValue) : $dimensionValue; - $contentRepository = $this->contentRepositoryRegistry->get($node->subgraphIdentity->contentRepositoryId); - - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($node->subgraphIdentity->contentStreamId); - if (is_null($workspace)) { + $contentRepository = $this->contentRepositoryRegistry->get($node->address->contentRepositoryId); + + try { + return $contentRepository + ->getContentGraph($node->address->workspaceName) + ->getSubgraph( + $node->address->dimensionSpacePoint->vary($contentDimensionId, $contentDimensionValue->value), + $node->subgraphIdentity->visibilityConstraints + )->findNodeById($node->nodeAggregateId); + } catch (WorkspaceDoesNotExist) { return null; } - // FIXME: node->workspaceName - return $contentRepository - ->getContentGraph($workspace->workspaceName) - ->getSubgraph( - $node->subgraphIdentity->dimensionSpacePoint->vary($contentDimensionId, $contentDimensionValue->value), - $node->subgraphIdentity->visibilityConstraints - )->findNodeById($node->nodeAggregateId); } public function allowsCallOfMethod($methodName): bool diff --git a/Neos.Neos/Classes/View/FusionExceptionView.php b/Neos.Neos/Classes/View/FusionExceptionView.php index 6a0fa255278..747c1d0188a 100644 --- a/Neos.Neos/Classes/View/FusionExceptionView.php +++ b/Neos.Neos/Classes/View/FusionExceptionView.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; @@ -105,27 +106,26 @@ public function render(): ResponseInterface|StreamInterface return $this->renderErrorWelcomeScreen(); } - $contentRepository = $this->contentRepositoryRegistry->get($siteDetectionResult->contentRepositoryId); $fusionExceptionViewInternals = $this->contentRepositoryRegistry->buildService( $siteDetectionResult->contentRepositoryId, new FusionExceptionViewInternalsFactory() ); $dimensionSpacePoint = $fusionExceptionViewInternals->getArbitraryDimensionSpacePoint(); - $liveWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive()); - - $currentSiteNode = null; $site = $this->siteRepository->findOneByNodeName($siteDetectionResult->siteNodeName); - if ($liveWorkspace && $site) { + + if (!$site) { + return $this->renderErrorWelcomeScreen(); + } + + try { $currentSiteNode = $this->siteNodeUtility->findSiteNodeBySite( $site, - $liveWorkspace->workspaceName, + WorkspaceName::forLive(), $dimensionSpacePoint, VisibilityConstraints::frontend() ); - } - - if (!$currentSiteNode) { + } catch (WorkspaceDoesNotExist|\RuntimeException $exception) { return $this->renderErrorWelcomeScreen(); } diff --git a/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php b/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php index 70056016538..481a7f693f1 100644 --- a/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php +++ b/Neos.TimeableNodeVisibility/Classes/Service/TimeableNodeVisibilityService.php @@ -42,14 +42,9 @@ class TimeableNodeVisibilityService public function handleExceededNodeDates(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName): ChangedVisibilities { $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); - $liveWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName($workspaceName); - if ($liveWorkspace === null) { - throw WorkspaceDoesNotExist::butWasSupposedTo($workspaceName); - } - $now = new \DateTimeImmutable(); - $nodes = $this->getNodesWithExceededDates($contentRepository, $liveWorkspace, $now); + $nodes = $this->getNodesWithExceededDates($contentRepository, $workspaceName, $now); $results = []; /** @var Node $node */ @@ -58,7 +53,7 @@ public function handleExceededNodeDates(ContentRepositoryId $contentRepositoryId if ($this->needsEnabling($node, $now) && $nodeIsDisabled) { $contentRepository->handle( EnableNodeAggregate::create( - $liveWorkspace->workspaceName, + $workspaceName, $node->nodeAggregateId, $node->subgraphIdentity->dimensionSpacePoint, NodeVariantSelectionStrategy::STRATEGY_ALL_SPECIALIZATIONS @@ -72,7 +67,7 @@ public function handleExceededNodeDates(ContentRepositoryId $contentRepositoryId if ($this->needsDisabling($node, $now) && !$nodeIsDisabled) { $contentRepository->handle( DisableNodeAggregate::create( - $liveWorkspace->workspaceName, + $workspaceName, $node->nodeAggregateId, $node->subgraphIdentity->dimensionSpacePoint, NodeVariantSelectionStrategy::STRATEGY_ALL_SPECIALIZATIONS @@ -89,13 +84,13 @@ public function handleExceededNodeDates(ContentRepositoryId $contentRepositoryId /** * @return \Generator */ - private function getNodesWithExceededDates(ContentRepository $contentRepository, Workspace $liveWorkspace, \DateTimeImmutable $now): \Generator + private function getNodesWithExceededDates(ContentRepository $contentRepository, WorkspaceName $workspaceName, \DateTimeImmutable $now): \Generator { $dimensionSpacePoints = $contentRepository->getVariationGraph()->getDimensionSpacePoints(); foreach ($dimensionSpacePoints as $dimensionSpacePoint) { - $contentGraph = $contentRepository->getContentGraph($liveWorkspace->workspaceName); + $contentGraph = $contentRepository->getContentGraph($workspaceName); // We fetch without restriction to get also all disabled nodes $subgraph = $contentGraph->getSubgraph( From 9d7f7220dd0cb298b909ac0efbf009bfde6df079 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 11 May 2024 15:06:56 +0200 Subject: [PATCH 192/214] TASK: Rename `address.nodeAggregateId` to `address.aggregateId` and deprecate `node.nodeAggregateId` --- .../src/Domain/Repository/NodeFactory.php | 3 +-- .../src/Domain/Repository/NodeFactory.php | 3 +-- .../Classes/Projection/ContentGraph/Node.php | 5 +++-- .../Classes/SharedModel/Node/NodeAddress.php | 18 +++++++++--------- .../Classes/Unit/NodeSubjectProvider.php | 3 +-- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php index a0f550ba75a..a8463e44351 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php @@ -79,7 +79,7 @@ public function mapNodeRowToNode( $this->contentRepositoryId, $workspaceName, $dimensionSpacePoint, - $nodeId = NodeAggregateId::fromString($nodeRow['nodeaggregateid']) + NodeAggregateId::fromString($nodeRow['nodeaggregateid']) ), ContentSubgraphIdentity::create( $this->contentRepositoryId, @@ -87,7 +87,6 @@ public function mapNodeRowToNode( $dimensionSpacePoint, $visibilityConstraints ), - $nodeId, $this->dimensionSpacePointRepository->getOriginDimensionSpacePointByHash($nodeRow['origindimensionspacepointhash']), NodeAggregateClassification::from($nodeRow['classification']), NodeTypeName::fromString($nodeRow['nodetypename']), diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php index a0ea99cb7c4..552a73e3783 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php @@ -84,7 +84,7 @@ public function mapNodeRowToNode( // todo use actual workspace name WorkspaceName::fromString('missing'), $dimensionSpacePoint ?: DimensionSpacePoint::fromJsonString($nodeRow['dimensionspacepoint']), - $nodeId = NodeAggregateId::fromString($nodeRow['nodeaggregateid']) + NodeAggregateId::fromString($nodeRow['nodeaggregateid']) ), ContentSubgraphIdentity::create( $this->contentRepositoryId, @@ -92,7 +92,6 @@ public function mapNodeRowToNode( $dimensionSpacePoint ?: DimensionSpacePoint::fromJsonString($nodeRow['dimensionspacepoint']), $visibilityConstraints ), - $nodeId, OriginDimensionSpacePoint::fromJsonString($nodeRow['origindimensionspacepoint']), NodeAggregateClassification::from($nodeRow['classification']), NodeTypeName::fromString($nodeRow['nodetypename']), diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index 5276789f1b8..2451cfcb777 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -58,6 +58,7 @@ private function __construct( public NodeAddress $address, /** @deprecated will be removed before the final 9.0 release */ public ContentSubgraphIdentity $subgraphIdentity, + /** @deprecated will be removed before the final 9.0 release (use address.aggregateId instead) */ public NodeAggregateId $nodeAggregateId, public OriginDimensionSpacePoint $originDimensionSpacePoint, public NodeAggregateClassification $classification, @@ -76,9 +77,9 @@ private function __construct( /** * @internal The signature of this method can change in the future! */ - public static function create(NodeAddress $address, ContentSubgraphIdentity $subgraphIdentity, NodeAggregateId $nodeAggregateId, OriginDimensionSpacePoint $originDimensionSpacePoint, NodeAggregateClassification $classification, NodeTypeName $nodeTypeName, ?NodeType $nodeType, PropertyCollection $properties, ?NodeName $nodeName, NodeTags $tags, Timestamps $timestamps): self + public static function create(NodeAddress $address, ContentSubgraphIdentity $subgraphIdentity, OriginDimensionSpacePoint $originDimensionSpacePoint, NodeAggregateClassification $classification, NodeTypeName $nodeTypeName, ?NodeType $nodeType, PropertyCollection $properties, ?NodeName $nodeName, NodeTags $tags, Timestamps $timestamps): self { - return new self($address, $subgraphIdentity, $nodeAggregateId, $originDimensionSpacePoint, $classification, $nodeTypeName, $nodeType, $properties, $nodeName, $tags, $timestamps); + return new self($address, $subgraphIdentity, $address->aggregateId, $originDimensionSpacePoint, $classification, $nodeTypeName, $nodeType, $properties, $nodeName, $tags, $timestamps); } /** diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php b/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php index 2c74d0e8210..675a760bb81 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php @@ -19,7 +19,7 @@ * $nodeAddress->dimensionSpacePoint, * VisibilityConstraints::withoutRestrictions() * ); - * $node = $subgraph->findNodeById($nodeAddress->nodeAggregateId); + * $node = $subgraph->findNodeById($nodeAddress->aggregateId); * * @api */ @@ -29,7 +29,7 @@ private function __construct( public ContentRepositoryId $contentRepositoryId, public WorkspaceName $workspaceName, public DimensionSpacePoint $dimensionSpacePoint, - public NodeAggregateId $nodeAggregateId, + public NodeAggregateId $aggregateId, ) { } @@ -37,9 +37,9 @@ public static function create( ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, DimensionSpacePoint $dimensionSpacePoint, - NodeAggregateId $nodeAggregateId, + NodeAggregateId $aggregateId, ): self { - return new self($contentRepositoryId, $workspaceName, $dimensionSpacePoint, $nodeAggregateId); + return new self($contentRepositoryId, $workspaceName, $dimensionSpacePoint, $aggregateId); } /** @@ -51,7 +51,7 @@ public static function fromArray(array $array): self ContentRepositoryId::fromString($array['contentRepositoryId']), WorkspaceName::fromString($array['workspaceName']), DimensionSpacePoint::fromArray($array['dimensionSpacePoint']), - NodeAggregateId::fromString($array['nodeAggregateId']) + NodeAggregateId::fromString($array['aggregateId']) ); } @@ -60,9 +60,9 @@ public static function fromJsonString(string $jsonString): self return self::fromArray(\json_decode($jsonString, true, JSON_THROW_ON_ERROR)); } - public function withNodeAggregateId(NodeAggregateId $nodeAggregateId): self + public function withAggregateId(NodeAggregateId $aggregateId): self { - return new self($this->contentRepositoryId, $this->workspaceName, $this->dimensionSpacePoint, $nodeAggregateId); + return new self($this->contentRepositoryId, $this->workspaceName, $this->dimensionSpacePoint, $aggregateId); } public function equals(self $other): bool @@ -70,7 +70,7 @@ public function equals(self $other): bool return $this->contentRepositoryId->equals($other->contentRepositoryId) && $this->workspaceName->equals($other->workspaceName) && $this->dimensionSpacePoint->equals($other->dimensionSpacePoint) - && $this->nodeAggregateId->equals($other->nodeAggregateId); + && $this->aggregateId->equals($other->aggregateId); } public function toJson(): string @@ -84,7 +84,7 @@ public function jsonSerialize(): mixed 'contentRepositoryId' => $this->contentRepositoryId, 'workspaceName' => $this->workspaceName, 'dimensionSpacePoint' => $this->dimensionSpacePoint, - 'nodeAggregateId' => $this->nodeAggregateId + 'aggregateId' => $this->aggregateId ]; } } diff --git a/Neos.ContentRepository.TestSuite/Classes/Unit/NodeSubjectProvider.php b/Neos.ContentRepository.TestSuite/Classes/Unit/NodeSubjectProvider.php index 705edad9aa9..3e9a4616e6a 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Unit/NodeSubjectProvider.php +++ b/Neos.ContentRepository.TestSuite/Classes/Unit/NodeSubjectProvider.php @@ -93,7 +93,7 @@ public function createMinimalNodeOfType( ContentRepositoryId::fromString('default'), WorkspaceName::forLive(), DimensionSpacePoint::createWithoutDimensions(), - $id = NodeAggregateId::create() + NodeAggregateId::create() ), ContentSubgraphIdentity::create( ContentRepositoryId::fromString('default'), @@ -101,7 +101,6 @@ public function createMinimalNodeOfType( DimensionSpacePoint::createWithoutDimensions(), VisibilityConstraints::withoutRestrictions() ), - $id, OriginDimensionSpacePoint::createWithoutDimensions(), NodeAggregateClassification::CLASSIFICATION_REGULAR, $nodeType->name, From 8d01b02808e661dffb29b807eef1128be361067b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 11 May 2024 15:35:56 +0200 Subject: [PATCH 193/214] TASK: Use workspace instead of `contentStreamId` as hash in flow-queries internally --- .../FlowQueryOperations/CreateNodeHashTrait.php | 10 +++++----- .../Classes/FlowQueryOperations/FindOperation.php | 8 +++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/CreateNodeHashTrait.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/CreateNodeHashTrait.php index 3e7e8b821f4..f392e57850b 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/CreateNodeHashTrait.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/CreateNodeHashTrait.php @@ -9,8 +9,8 @@ trait CreateNodeHashTrait { /** - * Create a string hash containing the nodeAggregateId, cr-id, contentStream->id, dimensionSpacePoint->hash - * and visibilityConstraints->hash. To be used for ensuring uniqueness or removing nodes. + * Create a string hash containing the node-aggregateId, cr-id, workspace-name, dimensionSpacePoint-hash + * and visibilityConstraints-hash. To be used for ensuring uniqueness or removing nodes. * * @see Node::equals() for comparison */ @@ -21,9 +21,9 @@ protected function createNodeHash(Node $node): string ':', [ $node->nodeAggregateId->value, - $node->subgraphIdentity->contentRepositoryId->value, - $node->subgraphIdentity->contentStreamId->value, - $node->subgraphIdentity->dimensionSpacePoint->hash, + $node->address->contentRepositoryId->value, + $node->address->workspaceName->value, + $node->address->dimensionSpacePoint->hash, $node->subgraphIdentity->visibilityConstraints->getHash() ] ) diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindOperation.php index ba8aa21e5ba..c01bc5fec87 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindOperation.php @@ -62,6 +62,8 @@ */ class FindOperation extends AbstractOperation { + use CreateNodeHashTrait; + /** * {@inheritdoc} * @@ -179,7 +181,7 @@ public function evaluate(FlowQuery $flowQuery, array $arguments): void $uniqueResult = []; $usedKeys = []; foreach ($result as $item) { - $identifier = $item->subgraphIdentity->contentStreamId->value . '@' . $item->subgraphIdentity->dimensionSpacePoint->hash . '@' . $item->nodeAggregateId->value; + $identifier = $this->createNodeHash($item); if (!isset($usedKeys[$identifier])) { $uniqueResult[] = $item; $usedKeys[$identifier] = $identifier; @@ -199,8 +201,8 @@ protected function getEntryPoints(array $contextNodes): array foreach ($contextNodes as $contextNode) { assert($contextNode instanceof Node); $subgraph = $this->contentRepositoryRegistry->subgraphForNode($contextNode); - $subgraphIdentifier = md5($contextNode->subgraphIdentity->contentStreamId->value - . '@' . $contextNode->subgraphIdentity->dimensionSpacePoint->toJson()); + $subgraphIdentifier = md5($subgraph->getIdentity()->contentStreamId->value + . '@' . $contextNode->address->dimensionSpacePoint->toJson()); if (!isset($entryPoints[(string) $subgraphIdentifier])) { $entryPoints[(string) $subgraphIdentifier] = [ 'subgraph' => $subgraph, From b8faeebed591226dd21e7dfa8d0818e86caf0655 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 11 May 2024 15:36:31 +0200 Subject: [PATCH 194/214] TASK: Adjust `NeosFusionContextSerializer` to use `NodeAddress` --- .../Cache/NeosFusionContextSerializer.php | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php index ee700b86748..18b7c724d06 100644 --- a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php +++ b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php @@ -89,7 +89,7 @@ private function tryDeserializeNode(array $serializedNode): ?Node return null; } - $node = $subgraph->findNodeById(NodeAggregateId::fromString($serializedNode['nodeAggregateId'])); + $node = $subgraph->findNodeById(NodeAggregateId::fromString($serializedNode['aggregateId'])); if (!$node) { // instead of crashing the whole rendering, by silently returning null we will most likely just break // rendering of the sub part here that needs the node @@ -104,22 +104,7 @@ private function tryDeserializeNode(array $serializedNode): ?Node */ private function serializeNode(Node $source): array { - $contentRepository = $this->contentRepositoryRegistry->get( - $source->subgraphIdentity->contentRepositoryId - ); - - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($source->subgraphIdentity->contentStreamId); - - if (!$workspace) { - throw new \RuntimeException(sprintf('Could not fetch workspace for node (%s) in content stream (%s).', $source->nodeAggregateId->value, $source->subgraphIdentity->contentStreamId->value), 1699780153); - } - - return [ - 'contentRepositoryId' => $source->subgraphIdentity->contentRepositoryId->value, - 'workspaceName' => $workspace->workspaceName->value, - 'dimensionSpacePoint' => $source->subgraphIdentity->dimensionSpacePoint->jsonSerialize(), - 'nodeAggregateId' => $source->nodeAggregateId->value - ]; + return $source->address->jsonSerialize(); } public function supportsDenormalization(mixed $data, string $type, string $format = null) From 61ca91e5158e3d530b49afc8e08952016d546a92 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 11 May 2024 15:46:43 +0200 Subject: [PATCH 195/214] TASK: Use `findOneByName` over `findOneByCurrentContentStreamId` --- Neos.Neos/Classes/Fusion/Helper/CachingHelper.php | 10 ++++------ Neos.Neos/Classes/Service/LinkingService.php | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php index 17cffd3c6a8..fec344dff6d 100644 --- a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php @@ -135,17 +135,15 @@ public function getWorkspaceChain(?Node $node): array } $contentRepository = $this->contentRepositoryRegistry->get( - $node->subgraphIdentity->contentRepositoryId + $node->address->contentRepositoryId ); - - /** @var Workspace $currentWorkspace */ - $currentWorkspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId( - $node->subgraphIdentity->contentStreamId + $currentWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName( + $node->address->workspaceName ); $workspaceChain = []; // TODO: Maybe write CTE here - while ($currentWorkspace instanceof Workspace) { + while ($currentWorkspace !== null) { $workspaceChain[$currentWorkspace->workspaceName->value] = $currentWorkspace; $currentWorkspace = $currentWorkspace->baseWorkspaceName ? $contentRepository->getWorkspaceFinder()->findOneByName($currentWorkspace->baseWorkspaceName) diff --git a/Neos.Neos/Classes/Service/LinkingService.php b/Neos.Neos/Classes/Service/LinkingService.php index 74f1c01ca94..bde665bb2a5 100644 --- a/Neos.Neos/Classes/Service/LinkingService.php +++ b/Neos.Neos/Classes/Service/LinkingService.php @@ -350,10 +350,10 @@ public function createNodeUri( $this->lastLinkedNode = $node; $contentRepository = $this->contentRepositoryRegistry->get( - $node->subgraphIdentity->contentRepositoryId + $node->address->contentRepositoryId ); - $workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId( - $node->subgraphIdentity->contentStreamId + $workspace = $contentRepository->getWorkspaceFinder()->findOneByName( + $node->address->workspaceName ); $request = $controllerContext->getRequest()->getMainRequest(); $uriBuilder = clone $controllerContext->getUriBuilder(); From 4d386134dc4a185562c82a934384dfe0e9863eb2 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 11 May 2024 15:57:44 +0200 Subject: [PATCH 196/214] TASK: Solve todo in `TetheredNodeAdjustments` --- .../src/Adjustment/TetheredNodeAdjustments.php | 13 +++++-------- .../src/StructureAdjustmentService.php | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index e80974464f0..73195ff0991 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -34,7 +34,6 @@ class TetheredNodeAdjustments use TetheredNodeInternals; public function __construct( - private readonly ContentRepository $contentRepository, private readonly ProjectedNodeIterator $projectedNodeIterator, private readonly NodeTypeManager $nodeTypeManager, private readonly DimensionSpace\InterDimensionalVariationGraph $interDimensionalVariationGraph, @@ -55,8 +54,6 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat $expectedTetheredNodes = $this->nodeTypeManager->getTetheredNodesConfigurationForNodeType($nodeType); foreach ($this->projectedNodeIterator->nodeAggregatesOfType($nodeTypeName) as $nodeAggregate) { - // TODO: We should use $nodeAggregate->workspaceName as soon as it's available - $contentGraph = $this->contentRepository->getContentGraph(WorkspaceName::forLive()); // find missing tethered nodes $foundMissingOrDisallowedTetheredNodes = false; $originDimensionSpacePoints = $nodeType->isOfType(NodeTypeName::ROOT_NODE_TYPE_NAME) @@ -69,7 +66,7 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat foreach ($expectedTetheredNodes as $tetheredNodeName => $expectedTetheredNodeType) { $tetheredNodeName = NodeName::fromString($tetheredNodeName); - $tetheredNode = $contentGraph->getSubgraph( + $tetheredNode = $this->projectedNodeIterator->contentGraph->getSubgraph( $originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions() )->findNodeByPath( @@ -86,9 +83,9 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat $nodeAggregate->nodeAggregateId, StructureAdjustment::TETHERED_NODE_MISSING, 'The tethered child node "' . $tetheredNodeName->value . '" is missing.', - function () use ($nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, $expectedTetheredNodeType, $contentGraph) { + function () use ($nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, $expectedTetheredNodeType) { $events = $this->createEventsForMissingTetheredNode( - $contentGraph, + $this->projectedNodeIterator->contentGraph, $nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, @@ -112,7 +109,7 @@ function () use ($nodeAggregate, $originDimensionSpacePoint, $tetheredNodeName, } // find disallowed tethered nodes - $tetheredNodeAggregates = $contentGraph->findTetheredChildNodeAggregates( + $tetheredNodeAggregates = $this->projectedNodeIterator->contentGraph->findTetheredChildNodeAggregates( $nodeAggregate->nodeAggregateId ); foreach ($tetheredNodeAggregates as $tetheredNodeAggregate) { @@ -134,7 +131,7 @@ function () use ($tetheredNodeAggregate) { // find wrongly ordered tethered nodes if ($foundMissingOrDisallowedTetheredNodes === false) { foreach ($originDimensionSpacePoints as $originDimensionSpacePoint) { - $childNodes = $contentGraph->getSubgraph($originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions())->findChildNodes($nodeAggregate->nodeAggregateId, FindChildNodesFilter::create()); + $childNodes = $this->projectedNodeIterator->contentGraph->getSubgraph($originDimensionSpacePoint->toDimensionSpacePoint(), VisibilityConstraints::withoutRestrictions())->findChildNodes($nodeAggregate->nodeAggregateId, FindChildNodesFilter::create()); /** is indexed by node name, and the value is the tethered node itself */ $actualTetheredChildNodes = []; diff --git a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php index f1b0222d607..3c671758d58 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php +++ b/Neos.ContentRepository.StructureAdjustment/src/StructureAdjustmentService.php @@ -41,7 +41,6 @@ public function __construct( ); $this->tetheredNodeAdjustments = new TetheredNodeAdjustments( - $contentRepository, $projectedNodeIterator, $nodeTypeManager, $interDimensionalVariationGraph, From 90adfd9ccaa51a20d519af813379db84032e2634 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 11 May 2024 17:33:32 +0200 Subject: [PATCH 197/214] TASK: Fix linting errors --- .../src/Domain/Repository/NodeFactory.php | 10 +++++----- Neos.Neos/Classes/View/FusionExceptionView.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php index a8463e44351..908b7e9e594 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php @@ -278,11 +278,11 @@ public function mapNodeRowsToNodeAggregates( // ... so we handle occupation exactly once ... $nodesByOccupiedDimensionSpacePointsByNodeAggregate [$rawNodeAggregateId][$occupiedDimensionSpacePoint->hash] = $this->mapNodeRowToNode( - $nodeRow, - $workspaceName, - $contentStreamId, - $occupiedDimensionSpacePoint->toDimensionSpacePoint(), - $visibilityConstraints + $nodeRow, + $workspaceName, + $contentStreamId, + $occupiedDimensionSpacePoint->toDimensionSpacePoint(), + $visibilityConstraints ); $occupiedDimensionSpacePointsByNodeAggregate[$rawNodeAggregateId][] = $occupiedDimensionSpacePoint; diff --git a/Neos.Neos/Classes/View/FusionExceptionView.php b/Neos.Neos/Classes/View/FusionExceptionView.php index 747c1d0188a..fd6814acf20 100644 --- a/Neos.Neos/Classes/View/FusionExceptionView.php +++ b/Neos.Neos/Classes/View/FusionExceptionView.php @@ -125,7 +125,7 @@ public function render(): ResponseInterface|StreamInterface $dimensionSpacePoint, VisibilityConstraints::frontend() ); - } catch (WorkspaceDoesNotExist|\RuntimeException $exception) { + } catch (WorkspaceDoesNotExist | \RuntimeException) { return $this->renderErrorWelcomeScreen(); } From d76604fa4f4ff92bbcf03bed33e8b63e533ada46 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 11 May 2024 20:43:58 +0200 Subject: [PATCH 198/214] TASK: Use `NodeAddress` fully in `NeosFusionContextSerializer` --- .../Cache/NeosFusionContextSerializer.php | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php index 18b7c724d06..4d5cc02405b 100644 --- a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php +++ b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php @@ -4,13 +4,10 @@ namespace Neos\Neos\Fusion\Cache; -use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; -use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; -use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Fusion\Core\Cache\FusionContextSerializer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -48,6 +45,7 @@ public function __construct( public function denormalize(mixed $data, string $type, string $format = null, array $context = []) { if ($type === Node::class) { + /** @var $data array */ return $this->tryDeserializeNode($data); } return $this->fusionContextSerializer->denormalize($data, $type, $format, $context); @@ -66,19 +64,18 @@ public function normalize(mixed $object, string $format = null, array $context = } /** - * @param array $serializedNode + * @param array $serializedNode */ private function tryDeserializeNode(array $serializedNode): ?Node { - $contentRepositoryId = ContentRepositoryId::fromString($serializedNode['contentRepositoryId']); + $nodeAddress = NodeAddress::fromArray($serializedNode); - $contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId); + $contentRepository = $this->contentRepositoryRegistry->get($nodeAddress->contentRepositoryId); try { - $workspaceName = WorkspaceName::fromString($serializedNode['workspaceName']); - $subgraph = $contentRepository->getContentGraph($workspaceName)->getSubgraph( - DimensionSpacePoint::fromArray($serializedNode['dimensionSpacePoint']), - $workspaceName->isLive() + $subgraph = $contentRepository->getContentGraph($nodeAddress->workspaceName)->getSubgraph( + $nodeAddress->dimensionSpacePoint, + $nodeAddress->workspaceName->isLive() ? VisibilityConstraints::frontend() : VisibilityConstraints::withoutRestrictions() ); @@ -89,7 +86,7 @@ private function tryDeserializeNode(array $serializedNode): ?Node return null; } - $node = $subgraph->findNodeById(NodeAggregateId::fromString($serializedNode['aggregateId'])); + $node = $subgraph->findNodeById($nodeAddress->aggregateId); if (!$node) { // instead of crashing the whole rendering, by silently returning null we will most likely just break // rendering of the sub part here that needs the node From aa7f207befcf5410237db70d5f5e84d5e0cc2be9 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 12 May 2024 11:46:39 +0200 Subject: [PATCH 199/214] TASK: Move node's "Read Model" identity properties first level on the node --- .../src/Domain/Repository/NodeFactory.php | 20 ++-- .../src/Domain/Repository/NodeFactory.php | 22 ++-- .../DimensionSpace/DimensionSpacePoint.php | 2 - .../ContentGraph/ContentSubgraphIdentity.php | 7 +- .../Classes/Projection/ContentGraph/Node.php | 104 +++++++++++++++--- .../Classes/SharedModel/Node/NodeAddress.php | 21 +++- .../CreateNodeHashTrait.php | 8 +- .../FlowQueryOperations/FindOperation.php | 2 +- .../Classes/Unit/NodeSubjectProvider.php | 22 ++-- .../Classes/ContentRepositoryRegistry.php | 8 +- .../Cache/NeosFusionContextSerializer.php | 2 +- .../DimensionsMenuItemsImplementation.php | 2 +- .../Classes/Fusion/Helper/CachingHelper.php | 4 +- .../Classes/Fusion/Helper/DimensionHelper.php | 10 +- Neos.Neos/Classes/Service/LinkingService.php | 4 +- .../Unit/Fusion/Helper/CachingHelperTest.php | 17 +-- 16 files changed, 162 insertions(+), 93 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php index 908b7e9e594..3e91d0fc05a 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/NodeFactory.php @@ -75,22 +75,13 @@ public function mapNodeRowToNode( : null; return Node::create( - NodeAddress::create( - $this->contentRepositoryId, - $workspaceName, - $dimensionSpacePoint, - NodeAggregateId::fromString($nodeRow['nodeaggregateid']) - ), - ContentSubgraphIdentity::create( - $this->contentRepositoryId, - $contentStreamId, - $dimensionSpacePoint, - $visibilityConstraints - ), + $this->contentRepositoryId, + $workspaceName, + $dimensionSpacePoint, + NodeAggregateId::fromString($nodeRow['nodeaggregateid']), $this->dimensionSpacePointRepository->getOriginDimensionSpacePointByHash($nodeRow['origindimensionspacepointhash']), NodeAggregateClassification::from($nodeRow['classification']), NodeTypeName::fromString($nodeRow['nodetypename']), - $nodeType, $this->createPropertyCollectionFromJsonString($nodeRow['properties']), isset($nodeRow['name']) ? NodeName::fromString($nodeRow['name']) : null, self::extractNodeTagsFromJson($nodeRow['subtreetags']), @@ -100,6 +91,9 @@ public function mapNodeRowToNode( isset($nodeRow['lastmodified']) ? self::parseDateTimeString($nodeRow['lastmodified']) : null, isset($nodeRow['originallastmodified']) ? self::parseDateTimeString($nodeRow['originallastmodified']) : null, ), + $visibilityConstraints, + $nodeType, + $contentStreamId ); } diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php index 552a73e3783..dbd29ff6642 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/NodeFactory.php @@ -79,23 +79,14 @@ public function mapNodeRowToNode( : null; return Node::create( - NodeAddress::create( - $this->contentRepositoryId, - // todo use actual workspace name - WorkspaceName::fromString('missing'), - $dimensionSpacePoint ?: DimensionSpacePoint::fromJsonString($nodeRow['dimensionspacepoint']), - NodeAggregateId::fromString($nodeRow['nodeaggregateid']) - ), - ContentSubgraphIdentity::create( - $this->contentRepositoryId, - $contentStreamId ?: ContentStreamId::fromString($nodeRow['contentstreamid']), - $dimensionSpacePoint ?: DimensionSpacePoint::fromJsonString($nodeRow['dimensionspacepoint']), - $visibilityConstraints - ), + $this->contentRepositoryId, + // todo use actual workspace name + WorkspaceName::fromString('missing'), + $dimensionSpacePoint ?: DimensionSpacePoint::fromJsonString($nodeRow['dimensionspacepoint']), + NodeAggregateId::fromString($nodeRow['nodeaggregateid']), OriginDimensionSpacePoint::fromJsonString($nodeRow['origindimensionspacepoint']), NodeAggregateClassification::from($nodeRow['classification']), NodeTypeName::fromString($nodeRow['nodetypename']), - $nodeType, new PropertyCollection( SerializedPropertyValues::fromJsonString($nodeRow['properties']), $this->propertyConverter @@ -110,6 +101,9 @@ public function mapNodeRowToNode( isset($nodeRow['lastmodified']) ? self::parseDateTimeString($nodeRow['lastmodified']) : null, isset($nodeRow['originallastmodified']) ? self::parseDateTimeString($nodeRow['originallastmodified']) : null, ), + $visibilityConstraints, + $nodeType, + $contentStreamId ?: ContentStreamId::fromString($nodeRow['contentstreamid']), ); } diff --git a/Neos.ContentRepository.Core/Classes/DimensionSpace/DimensionSpacePoint.php b/Neos.ContentRepository.Core/Classes/DimensionSpace/DimensionSpacePoint.php index 9a31aef29ad..31532f5e148 100644 --- a/Neos.ContentRepository.Core/Classes/DimensionSpace/DimensionSpacePoint.php +++ b/Neos.ContentRepository.Core/Classes/DimensionSpace/DimensionSpacePoint.php @@ -19,8 +19,6 @@ /** * A point in the dimension space with coordinates DimensionName => DimensionValue. * E.g.: ["language" => "es", "country" => "ar"] - * - * Implements CacheAwareInterface because of Fusion Runtime caching and Routing * @api */ final class DimensionSpacePoint extends AbstractDimensionSpacePoint diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphIdentity.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphIdentity.php index 40b35d78116..8c00f8e1268 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphIdentity.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphIdentity.php @@ -4,6 +4,7 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; /** @@ -31,7 +32,8 @@ * usually need this method instead of the Origin DimensionSpacePoint inside the read model; and you'll * need the OriginDimensionSpacePoint when constructing commands on the write side. * - * @api + * @deprecated please use the {@see NodeAddress} instead {@see Node::$subgraphIdentity}. Will be removed before the Final Neos 9 release. + * @internal */ final readonly class ContentSubgraphIdentity implements \JsonSerializable { @@ -46,9 +48,6 @@ private function __construct( ) { } - /** - * @api - */ public static function create( ContentRepositoryId $contentRepositoryId, ContentStreamId $contentStreamId, diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index 2451cfcb777..58161431d57 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -14,13 +14,17 @@ namespace Neos\ContentRepository\Core\Projection\ContentGraph; +use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateClassification; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; +use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * Main read model of the {@see ContentSubgraphInterface}. @@ -28,23 +32,80 @@ * Immutable, Read Only. In case you want to modify it, you need * to create Commands and send them to ContentRepository::handle. * + * ## Identity of a Node + * + * The node's "Read Model" identity is summarized here {@see NodeAddress}, consisting of: + * + * - {@see ContentRepositoryId} + * - {@see WorkspaceName} + * - {@see DimensionSpacePoint} + * - {@see NodeAggregateId} + * + * The node address can be constructed via {@see NodeAddress::fromNode()} and serialized. + * + * ## Traversing the graph + * * The node does not have structure information, i.e. no infos * about its children. To f.e. fetch children, you need to fetch - * the subgraph {@see ContentGraphInterface::getSubgraph()} via - * $subgraphIdentity {@see Node::$subgraphIdentity}. and then - * call findChildNodes() {@see ContentSubgraphInterface::findChildNodes()} - * on the subgraph. + * the subgraph and use findChildNodes on the subgraph: * - * The identity of a node is summarized here {@see NodeAddress} + * $subgraph = $contentRepository->getContentGraph($node->workspaceName)->getSubgraph( + * $node->dimensionSpacePoint, + * $node->visibilityConstraints + * ); + * $childNodes = $subgraph->findChildNodes($node->aggregateId, FindChildNodesFilter::create()); + * + * ## A note about the {@see DimensionSpacePoint} and the {@see OriginDimensionSpacePoint} + * + * The {@see Node::dimensionSpacePoint} is the DimensionSpacePoint this node has been accessed in, + * and NOT the DimensionSpacePoint where the node is "at home". + * The DimensionSpacePoint where the node is (at home) is called the ORIGIN DimensionSpacePoint, + * and this can be accessed using {@see Node::originDimensionSpacePoint}. If in doubt, you'll + * usually need the DimensionSpacePoint instead of the OriginDimensionSpacePoint; + * you'll only need the OriginDimensionSpacePoint when constructing commands on the write side. * * @api Note: The constructor is not part of the public API */ final readonly class Node { /** - * @param NodeAddress $address The node's "Read Model" identity. - * @param ContentSubgraphIdentity $subgraphIdentity This is part of the node's "Read Model" identity which is defined by: {@see self::subgraphIdentity} and {@see self::nodeAggregateId}. With this information, you can fetch a Subgraph using {@see ContentGraphInterface::getSubgraph()}. - * @param NodeAggregateId $nodeAggregateId NodeAggregateId (identifier) of this node. This is part of the node's "Read Model" identity which is defined by: {@see self::subgraphIdentity} and {@see self::nodeAggregateId} + * This was intermediate part of the node's "Read Model" identity. + * Please use {@see $contentRepositoryId} {@see $workspaceName} {@see $dimensionSpacePoint} and {@see $aggregateId} instead, + * or see {@see NodeAddress::fromNode()} for passing the identity around. + * The visibility-constraints now reside in {@see $visibilityConstraints}. + * There is no replacement for the previously attached content-stream-id. Please refactor the code to use the newly available workspace-name. + * @deprecated will be removed before the final 9.0 release + */ + public ContentSubgraphIdentity $subgraphIdentity; + + /** + * In PHP please use {@see $aggregateId} instead. + * + * For Fusion please use the upcoming FlowQuery operation: + * ``` + * ${q(node).id()} + * ``` + * @deprecated will be removed before the final 9.0 release + */ + public NodeAggregateId $nodeAggregateId; + + /** + * In PHP please fetch the NodeType via the NodeTypeManager or the NodeTypeWithFallbackProvider trait instead. + * {@see $nodeTypeName} + * + * For Fusion please use the EEL Helper: + * ``` + * ${Neos.Node.getNodeType(node)} + * ``` + * @deprecated will be removed before the final 9.0 release + */ + public ?NodeType $nodeType; + + /** + * @param ContentRepositoryId $contentRepositoryId The content-repository this Node belongs to + * @param WorkspaceName $workspaceName The workspace of this Node + * @param DimensionSpacePoint $dimensionSpacePoint DimensionSpacePoint a node has been accessed in + * @param NodeAggregateId $aggregateId NodeAggregateId (identifier) of this node. This is part of the node's "Read Model" identity, which is defined in {@see NodeAddress} * @param OriginDimensionSpacePoint $originDimensionSpacePoint The DimensionSpacePoint the node originates in. Usually needed to address a Node in a NodeAggregate in order to update it. * @param NodeAggregateClassification $classification The classification (regular, root, tethered) of this node * @param NodeTypeName $nodeTypeName The node's node type name; always set, even if unknown to the NodeTypeManager @@ -53,33 +114,44 @@ * @param NodeName|null $nodeName The optional name of the node, describing its relation to its parent * @param NodeTags $tags explicit and inherited SubtreeTags of this node * @param Timestamps $timestamps Creation and modification timestamps of this node + * @param VisibilityConstraints $visibilityConstraints Information which subgraph filter was used to access this node */ private function __construct( - public NodeAddress $address, - /** @deprecated will be removed before the final 9.0 release */ - public ContentSubgraphIdentity $subgraphIdentity, - /** @deprecated will be removed before the final 9.0 release (use address.aggregateId instead) */ - public NodeAggregateId $nodeAggregateId, + public ContentRepositoryId $contentRepositoryId, + public WorkspaceName $workspaceName, + public DimensionSpacePoint $dimensionSpacePoint, + public NodeAggregateId $aggregateId, public OriginDimensionSpacePoint $originDimensionSpacePoint, public NodeAggregateClassification $classification, public NodeTypeName $nodeTypeName, - public ?NodeType $nodeType, public PropertyCollection $properties, public ?NodeName $nodeName, public NodeTags $tags, public Timestamps $timestamps, + public VisibilityConstraints $visibilityConstraints, + ?NodeType $nodeType, + ContentStreamId $contentStreamId ) { if ($this->classification->isTethered() && $this->nodeName === null) { throw new \InvalidArgumentException('The NodeName must be set if the Node is tethered.', 1695118377); } + // legacy to be removed before Neos9 + $this->nodeAggregateId = $this->aggregateId; + $this->nodeType = $nodeType; + $this->subgraphIdentity = ContentSubgraphIdentity::create( + $contentRepositoryId, + $contentStreamId, + $dimensionSpacePoint, + $visibilityConstraints + ); } /** * @internal The signature of this method can change in the future! */ - public static function create(NodeAddress $address, ContentSubgraphIdentity $subgraphIdentity, OriginDimensionSpacePoint $originDimensionSpacePoint, NodeAggregateClassification $classification, NodeTypeName $nodeTypeName, ?NodeType $nodeType, PropertyCollection $properties, ?NodeName $nodeName, NodeTags $tags, Timestamps $timestamps): self + public static function create(ContentRepositoryId $contentRepositoryId, WorkspaceName $workspaceName, DimensionSpacePoint $dimensionSpacePoint, NodeAggregateId $aggregateId, OriginDimensionSpacePoint $originDimensionSpacePoint, NodeAggregateClassification $classification, NodeTypeName $nodeTypeName, PropertyCollection $properties, ?NodeName $nodeName, NodeTags $tags, Timestamps $timestamps, VisibilityConstraints $visibilityConstraints, ?NodeType $nodeType, ContentStreamId $contentStreamId): self { - return new self($address, $subgraphIdentity, $address->aggregateId, $originDimensionSpacePoint, $classification, $nodeTypeName, $nodeType, $properties, $nodeName, $tags, $timestamps); + return new self($contentRepositoryId, $workspaceName, $dimensionSpacePoint, $aggregateId, $originDimensionSpacePoint, $classification, $nodeTypeName, $properties, $nodeName, $tags, $timestamps, $visibilityConstraints, $nodeType, $contentStreamId); } /** diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php b/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php index 675a760bb81..f95883cf239 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php @@ -5,12 +5,19 @@ namespace Neos\ContentRepository\Core\SharedModel\Node; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; +use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** - * The content-repository-id workspace-name dimension-space-point and the node-aggregate-id - * Are used in combination to distinctly identify a single node. + * This describes a node's read model identity namely: + * + * - {@see ContentRepositoryId} + * - {@see WorkspaceName} + * - {@see DimensionSpacePoint} (not to be confused with the {@see Node::$originDimensionSpacePoint}) + * - {@see NodeAggregateId} + * + * In combination the parts can be used to distinctly identify a single node. * * By using the content graph for the content repository * one can build a subgraph with the right perspective to find this node: @@ -42,6 +49,16 @@ public static function create( return new self($contentRepositoryId, $workspaceName, $dimensionSpacePoint, $aggregateId); } + public static function fromNode(Node $node): self + { + return new self( + $node->contentRepositoryId, + $node->workspaceName, + $node->dimensionSpacePoint, + $node->nodeAggregateId + ); + } + /** * @param array $array */ diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/CreateNodeHashTrait.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/CreateNodeHashTrait.php index f392e57850b..8475f3ddcbd 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/CreateNodeHashTrait.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/CreateNodeHashTrait.php @@ -21,10 +21,10 @@ protected function createNodeHash(Node $node): string ':', [ $node->nodeAggregateId->value, - $node->address->contentRepositoryId->value, - $node->address->workspaceName->value, - $node->address->dimensionSpacePoint->hash, - $node->subgraphIdentity->visibilityConstraints->getHash() + $node->contentRepositoryId->value, + $node->workspaceName->value, + $node->dimensionSpacePoint->hash, + $node->visibilityConstraints->getHash() ] ) ); diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindOperation.php index c01bc5fec87..4b5c511b7e0 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindOperation.php @@ -202,7 +202,7 @@ protected function getEntryPoints(array $contextNodes): array assert($contextNode instanceof Node); $subgraph = $this->contentRepositoryRegistry->subgraphForNode($contextNode); $subgraphIdentifier = md5($subgraph->getIdentity()->contentStreamId->value - . '@' . $contextNode->address->dimensionSpacePoint->toJson()); + . '@' . $contextNode->dimensionSpacePoint->toJson()); if (!isset($entryPoints[(string) $subgraphIdentifier])) { $entryPoints[(string) $subgraphIdentifier] = [ 'subgraph' => $subgraph, diff --git a/Neos.ContentRepository.TestSuite/Classes/Unit/NodeSubjectProvider.php b/Neos.ContentRepository.TestSuite/Classes/Unit/NodeSubjectProvider.php index 3e9a4616e6a..ac74c544c0b 100644 --- a/Neos.ContentRepository.TestSuite/Classes/Unit/NodeSubjectProvider.php +++ b/Neos.ContentRepository.TestSuite/Classes/Unit/NodeSubjectProvider.php @@ -89,22 +89,13 @@ public function createMinimalNodeOfType( ): Node { $serializedDefaultPropertyValues = SerializedPropertyValues::defaultFromNodeType($nodeType, $this->propertyConverter); return Node::create( - NodeAddress::create( - ContentRepositoryId::fromString('default'), - WorkspaceName::forLive(), - DimensionSpacePoint::createWithoutDimensions(), - NodeAggregateId::create() - ), - ContentSubgraphIdentity::create( - ContentRepositoryId::fromString('default'), - ContentStreamId::fromString('cs-id'), - DimensionSpacePoint::createWithoutDimensions(), - VisibilityConstraints::withoutRestrictions() - ), + ContentRepositoryId::fromString('default'), + WorkspaceName::forLive(), + DimensionSpacePoint::createWithoutDimensions(), + NodeAggregateId::create(), OriginDimensionSpacePoint::createWithoutDimensions(), NodeAggregateClassification::CLASSIFICATION_REGULAR, $nodeType->name, - $nodeType, new PropertyCollection( $propertyValues ? $serializedDefaultPropertyValues->merge($propertyValues) @@ -118,7 +109,10 @@ public function createMinimalNodeOfType( new \DateTimeImmutable(), new \DateTimeImmutable(), new \DateTimeImmutable() - ) + ), + VisibilityConstraints::withoutRestrictions(), + $nodeType, + ContentStreamId::fromString('cs-id'), ); } } diff --git a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php index 412abb37eb7..4900eb13451 100644 --- a/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php +++ b/Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php @@ -92,11 +92,11 @@ public function resetFactoryInstance(ContentRepositoryId $contentRepositoryId): public function subgraphForNode(Node $node): ContentSubgraphInterface { - $contentRepository = $this->get($node->address->contentRepositoryId); + $contentRepository = $this->get($node->contentRepositoryId); - return $contentRepository->getContentGraph($node->address->workspaceName)->getSubgraph( - $node->subgraphIdentity->dimensionSpacePoint, - $node->subgraphIdentity->visibilityConstraints + return $contentRepository->getContentGraph($node->workspaceName)->getSubgraph( + $node->dimensionSpacePoint, + $node->visibilityConstraints ); } diff --git a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php index 4d5cc02405b..ac47b1e3172 100644 --- a/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php +++ b/Neos.Neos/Classes/Fusion/Cache/NeosFusionContextSerializer.php @@ -101,7 +101,7 @@ private function tryDeserializeNode(array $serializedNode): ?Node */ private function serializeNode(Node $source): array { - return $source->address->jsonSerialize(); + return NodeAddress::fromNode($source)->jsonSerialize(); } public function supportsDenormalization(mixed $data, string $type, string $format = null) diff --git a/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php index 7c1c3e77346..0f0ada32061 100644 --- a/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/DimensionsMenuItemsImplementation.php @@ -61,7 +61,7 @@ protected function buildItems(): array $currentDimensionSpacePoint = $currentNode->subgraphIdentity->dimensionSpacePoint; $contentDimensionIdentifierToLimitTo = $this->getContentDimensionIdentifierToLimitTo(); try { - $contentGraph = $contentRepository->getContentGraph($currentNode->address->workspaceName); + $contentGraph = $contentRepository->getContentGraph($currentNode->workspaceName); } catch (WorkspaceDoesNotExist) { return $menuItems; } diff --git a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php index fec344dff6d..6da6b577a39 100644 --- a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php @@ -135,11 +135,11 @@ public function getWorkspaceChain(?Node $node): array } $contentRepository = $this->contentRepositoryRegistry->get( - $node->address->contentRepositoryId + $node->contentRepositoryId ); $currentWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName( - $node->address->workspaceName + $node->workspaceName ); $workspaceChain = []; // TODO: Maybe write CTE here diff --git a/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php b/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php index 5653035ed9c..de4c5b0b691 100644 --- a/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/DimensionHelper.php @@ -143,15 +143,15 @@ public function findVariantInDimension(Node $node, ContentDimensionId|string $di { $contentDimensionId = is_string($dimensionName) ? new ContentDimensionId($dimensionName) : $dimensionName; $contentDimensionValue = is_string($dimensionValue) ? new ContentDimensionValue($dimensionValue) : $dimensionValue; - $contentRepository = $this->contentRepositoryRegistry->get($node->address->contentRepositoryId); + $contentRepository = $this->contentRepositoryRegistry->get($node->contentRepositoryId); try { return $contentRepository - ->getContentGraph($node->address->workspaceName) + ->getContentGraph($node->workspaceName) ->getSubgraph( - $node->address->dimensionSpacePoint->vary($contentDimensionId, $contentDimensionValue->value), - $node->subgraphIdentity->visibilityConstraints - )->findNodeById($node->nodeAggregateId); + $node->dimensionSpacePoint->vary($contentDimensionId, $contentDimensionValue->value), + $node->visibilityConstraints + )->findNodeById($node->aggregateId); } catch (WorkspaceDoesNotExist) { return null; } diff --git a/Neos.Neos/Classes/Service/LinkingService.php b/Neos.Neos/Classes/Service/LinkingService.php index bde665bb2a5..e9553c778d9 100644 --- a/Neos.Neos/Classes/Service/LinkingService.php +++ b/Neos.Neos/Classes/Service/LinkingService.php @@ -350,10 +350,10 @@ public function createNodeUri( $this->lastLinkedNode = $node; $contentRepository = $this->contentRepositoryRegistry->get( - $node->address->contentRepositoryId + $node->contentRepositoryId ); $workspace = $contentRepository->getWorkspaceFinder()->findOneByName( - $node->address->workspaceName + $node->workspaceName ); $request = $controllerContext->getRequest()->getMainRequest(); $uriBuilder = clone $controllerContext->getUriBuilder(); diff --git a/Neos.Neos/Tests/Unit/Fusion/Helper/CachingHelperTest.php b/Neos.Neos/Tests/Unit/Fusion/Helper/CachingHelperTest.php index 39593920d4c..15a787667d8 100644 --- a/Neos.Neos/Tests/Unit/Fusion/Helper/CachingHelperTest.php +++ b/Neos.Neos/Tests/Unit/Fusion/Helper/CachingHelperTest.php @@ -14,6 +14,8 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTags; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepository\Domain\Model\Workspace; use Neos\ContentRepository\Domain\Service\Context; use Neos\Flow\Tests\UnitTestCase; @@ -198,21 +200,20 @@ private function createNode(NodeAggregateId $nodeAggregateId): Node { $now = new \DateTimeImmutable(); return Node::create( - ContentSubgraphIdentity::create( - ContentRepositoryId::fromString("default"), - ContentStreamId::fromString("cs-identifier"), - DimensionSpacePoint::createWithoutDimensions(), - VisibilityConstraints::withoutRestrictions() - ), + ContentRepositoryId::fromString("default"), + WorkspaceName::forLive(), + DimensionSpacePoint::createWithoutDimensions(), $nodeAggregateId, OriginDimensionSpacePoint::createWithoutDimensions(), NodeAggregateClassification::CLASSIFICATION_REGULAR, NodeTypeName::fromString("SomeNodeTypeName"), - null, new PropertyCollection(SerializedPropertyValues::fromArray([]), new PropertyConverter(new Serializer([], []))), null, NodeTags::createEmpty(), - Timestamps::create($now, $now, null, null) + Timestamps::create($now, $now, null, null), + VisibilityConstraints::withoutRestrictions(), + null, + ContentStreamId::fromString("cs-identifier"), ); } } From ed5e10258a654816e669984d625aa66028ca552d Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 12 May 2024 11:49:48 +0200 Subject: [PATCH 200/214] TASK: Adjust Node::equals check --- .../Classes/Projection/ContentGraph/Node.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index 58161431d57..25eed6f86d1 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -190,9 +190,14 @@ public function getLabel(): string return $this->nodeType?->getNodeLabelGenerator()->getLabel($this) ?: $this->nodeTypeName->value; } + /** + * Checks if the node's "Read Model" identity equals with the given one + */ public function equals(Node $other): bool { - return $this->subgraphIdentity->equals($other->subgraphIdentity) - && $this->nodeAggregateId->equals($other->nodeAggregateId); + return $this->contentRepositoryId->equals($other->contentRepositoryId) + && $this->workspaceName->equals($other->workspaceName) + && $this->dimensionSpacePoint->equals($other->dimensionSpacePoint) + && $this->aggregateId->equals($other->aggregateId); } } From 4b92fc026d5c17680e8cf872a76694ce93032c64 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 12:29:34 +0200 Subject: [PATCH 201/214] TASK: Rename node.nodeName to node.name --- .../Classes/Projection/ContentGraph/Node.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index 25eed6f86d1..70a79fc5fa8 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -89,6 +89,17 @@ */ public NodeAggregateId $nodeAggregateId; + /** + * In PHP please use {@see $name} instead. + * + * For Fusion use: + * ``` + * ${node.name.value} + * ``` + * @deprecated will be removed before the final 9.0 release + */ + public ?NodeName $nodeName; + /** * In PHP please fetch the NodeType via the NodeTypeManager or the NodeTypeWithFallbackProvider trait instead. * {@see $nodeTypeName} @@ -111,7 +122,7 @@ * @param NodeTypeName $nodeTypeName The node's node type name; always set, even if unknown to the NodeTypeManager * @param NodeType|null $nodeType The node's node type, null if unknown to the NodeTypeManager - @deprecated Don't rely on this too much, as the capabilities of the NodeType here will probably change a lot; Ask the {@see NodeTypeManager} instead * @param PropertyCollection $properties All properties of this node. References are NOT part of this API; To access references, {@see ContentSubgraphInterface::findReferences()} can be used; To read the serialized properties use {@see PropertyCollection::serialized()}. - * @param NodeName|null $nodeName The optional name of the node, describing its relation to its parent + * @param NodeName|null $name The optional name of the node, describing its relation to its parent * @param NodeTags $tags explicit and inherited SubtreeTags of this node * @param Timestamps $timestamps Creation and modification timestamps of this node * @param VisibilityConstraints $visibilityConstraints Information which subgraph filter was used to access this node @@ -125,18 +136,19 @@ private function __construct( public NodeAggregateClassification $classification, public NodeTypeName $nodeTypeName, public PropertyCollection $properties, - public ?NodeName $nodeName, + public ?NodeName $name, public NodeTags $tags, public Timestamps $timestamps, public VisibilityConstraints $visibilityConstraints, ?NodeType $nodeType, ContentStreamId $contentStreamId ) { - if ($this->classification->isTethered() && $this->nodeName === null) { + if ($this->classification->isTethered() && $this->name === null) { throw new \InvalidArgumentException('The NodeName must be set if the Node is tethered.', 1695118377); } // legacy to be removed before Neos9 $this->nodeAggregateId = $this->aggregateId; + $this->nodeName = $this->name; $this->nodeType = $nodeType; $this->subgraphIdentity = ContentSubgraphIdentity::create( $contentRepositoryId, From 95ea6864b50410d7a00a876f29a1150325776429 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 15:09:41 +0200 Subject: [PATCH 202/214] TASK: Replace ContentSubgraphIdentity in subgraph --- .../src/Domain/Repository/ContentGraph.php | 17 +++++---- .../src/Domain/Repository/ContentSubgraph.php | 34 +++++++++++++----- .../Domain/Repository/ContentHypergraph.php | 16 ++++++--- .../Repository/ContentSubhypergraph.php | 36 ++++++++++++++----- .../ContentGraph/ContentGraphInterface.php | 15 ++++++-- .../ContentSubgraphWithRuntimeCaches.php | 29 ++++++++++++--- .../ContentGraph/ContentSubgraphInterface.php | 26 ++++++++------ .../FlowQueryOperations/FindOperation.php | 4 +-- 8 files changed, 127 insertions(+), 50 deletions(-) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 326f5e52f9c..611afd7c761 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -84,6 +84,16 @@ public function __construct( $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $this->tableNames); } + public function getContentRepositoryId(): ContentRepositoryId + { + return $this->contentRepositoryId; + } + + public function getWorkspaceName(): WorkspaceName + { + return $this->workspaceName; + } + public function getSubgraph( DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints @@ -362,13 +372,6 @@ private function fetchRows(QueryBuilder $queryBuilder): array } } - /** The workspace this content graph is operating on */ - public function getWorkspaceName(): WorkspaceName - { - return $this->workspaceName; - } - - /** @internal The content stream id where the workspace name points to for this instance */ public function getContentStreamId(): ContentStreamId { return $this->contentStreamId; diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php index 3e003e51bbb..322ed95e789 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentSubgraph.php @@ -104,14 +104,24 @@ public function __construct( $this->nodeQueryBuilder = new NodeQueryBuilder($this->client->getConnection(), $tableNames); } - public function getIdentity(): ContentSubgraphIdentity + public function getContentRepositoryId(): ContentRepositoryId { - return ContentSubgraphIdentity::create( - $this->contentRepositoryId, - $this->contentStreamId, - $this->dimensionSpacePoint, - $this->visibilityConstraints - ); + return $this->contentRepositoryId; + } + + public function getWorkspaceName(): WorkspaceName + { + return $this->workspaceName; + } + + public function getDimensionSpacePoint(): DimensionSpacePoint + { + return $this->dimensionSpacePoint; + } + + public function getVisibilityConstraints(): VisibilityConstraints + { + return $this->visibilityConstraints; } public function findChildNodes(NodeAggregateId $parentNodeAggregateId, FindChildNodesFilter $filter): Nodes @@ -429,9 +439,15 @@ public function countNodes(): int return $result; } - public function jsonSerialize(): ContentSubgraphIdentity + /** + * @return array + */ + public function jsonSerialize(): array { - return $this->getIdentity(); + return [ + 'workspaceName' => $this->workspaceName, + 'dimensionSpacePoint' => $this->dimensionSpacePoint, + ]; } /** ------------------------------------------- */ diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php index 678b3d69adc..8ed8bc21557 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentHypergraph.php @@ -68,6 +68,16 @@ public function __construct( $this->nodeFactory = $nodeFactory; } + public function getContentRepositoryId(): ContentRepositoryId + { + return $this->contentRepositoryId; + } + + public function getWorkspaceName(): WorkspaceName + { + return $this->workspaceName; + } + public function getSubgraph( DimensionSpacePoint $dimensionSpacePoint, VisibilityConstraints $visibilityConstraints @@ -77,6 +87,7 @@ public function getSubgraph( $this->subhypergraphs[$index] = new ContentSubhypergraph( $this->contentRepositoryId, $this->contentStreamId, + $this->workspaceName, $dimensionSpacePoint, $visibilityConstraints, $this->databaseClient, @@ -307,11 +318,6 @@ private function getDatabaseConnection(): DatabaseConnection return $this->databaseClient->getConnection(); } - public function getWorkspaceName(): WorkspaceName - { - return $this->workspaceName; - } - public function getContentStreamId(): ContentStreamId { return $this->contentStreamId; diff --git a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentSubhypergraph.php b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentSubhypergraph.php index 95d96cbf077..b25c7452294 100644 --- a/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentSubhypergraph.php +++ b/Neos.ContentGraph.PostgreSQLAdapter/src/Domain/Repository/ContentSubhypergraph.php @@ -52,6 +52,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * The content subgraph application repository @@ -76,6 +77,7 @@ public function __construct( private ContentRepositoryId $contentRepositoryId, private ContentStreamId $contentStreamId, + private WorkspaceName $workspaceName, private DimensionSpacePoint $dimensionSpacePoint, private VisibilityConstraints $visibilityConstraints, private PostgresDbalClientInterface $databaseClient, @@ -85,14 +87,24 @@ public function __construct( ) { } - public function getIdentity(): ContentSubgraphIdentity + public function getContentRepositoryId(): ContentRepositoryId { - return ContentSubgraphIdentity::create( - $this->contentRepositoryId, - $this->contentStreamId, - $this->dimensionSpacePoint, - $this->visibilityConstraints - ); + return $this->contentRepositoryId; + } + + public function getWorkspaceName(): WorkspaceName + { + return $this->workspaceName; + } + + public function getDimensionSpacePoint(): DimensionSpacePoint + { + return $this->dimensionSpacePoint; + } + + public function getVisibilityConstraints(): VisibilityConstraints + { + return $this->visibilityConstraints; } public function findNodeById(NodeAggregateId $nodeAggregateId): ?Node @@ -534,8 +546,14 @@ private function getDatabaseConnection(): DatabaseConnection return $this->databaseClient->getConnection(); } - public function jsonSerialize(): ContentSubgraphIdentity + /** + * @return array + */ + public function jsonSerialize(): array { - return $this->getIdentity(); + return [ + 'workspaceName' => $this->workspaceName, + 'dimensionSpacePoint' => $this->dimensionSpacePoint, + ]; } } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php index 4913da031cf..6a5ef9a7948 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphInterface.php @@ -19,6 +19,7 @@ use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ProjectionStateInterface; +use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregatesTypeIsAmbiguous; use Neos\ContentRepository\Core\SharedModel\Exception\RootNodeAggregateDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; @@ -37,6 +38,17 @@ */ interface ContentGraphInterface extends ProjectionStateInterface { + /** + * @api + */ + public function getContentRepositoryId(): ContentRepositoryId; + + /** + * The workspace this content graph is operating on + * @api + */ + public function getWorkspaceName(): WorkspaceName; + /** * @api main API method of ContentGraph */ @@ -149,9 +161,6 @@ public function getDimensionSpacePointsOccupiedByChildNodeName( */ public function countNodes(): int; - /** The workspace this content graph is operating on */ - public function getWorkspaceName(): WorkspaceName; - /** @internal The content stream id where the workspace name points to for this instance */ public function getContentStreamId(): ContentStreamId; } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphWithRuntimeCaches/ContentSubgraphWithRuntimeCaches.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphWithRuntimeCaches/ContentSubgraphWithRuntimeCaches.php index 96ec21720d7..72510b43e62 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphWithRuntimeCaches/ContentSubgraphWithRuntimeCaches.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentGraphWithRuntimeCaches/ContentSubgraphWithRuntimeCaches.php @@ -14,9 +14,9 @@ namespace Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphWithRuntimeCaches; +use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\AbsoluteNodePath; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphIdentity; use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter; use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\CountBackReferencesFilter; @@ -34,8 +34,11 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Nodes; use Neos\ContentRepository\Core\Projection\ContentGraph\References; use Neos\ContentRepository\Core\Projection\ContentGraph\Subtree; +use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; +use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * Wrapper for a concrete implementation of the {@see ContentSubgraphInterface} that @@ -53,9 +56,24 @@ public function __construct( $this->inMemoryCache = new InMemoryCache(); } - public function getIdentity(): ContentSubgraphIdentity + public function getContentRepositoryId(): ContentRepositoryId { - return $this->wrappedContentSubgraph->getIdentity(); + return $this->wrappedContentSubgraph->getContentRepositoryId(); + } + + public function getWorkspaceName(): WorkspaceName + { + return $this->wrappedContentSubgraph->getWorkspaceName(); + } + + public function getDimensionSpacePoint(): DimensionSpacePoint + { + return $this->wrappedContentSubgraph->getDimensionSpacePoint(); + } + + public function getVisibilityConstraints(): VisibilityConstraints + { + return $this->wrappedContentSubgraph->getVisibilityConstraints(); } public function findChildNodes(NodeAggregateId $parentNodeAggregateId, FindChildNodesFilter $filter): Nodes @@ -247,7 +265,10 @@ private static function isFilterEmpty(object $filter): bool return array_filter(get_object_vars($filter), static fn ($value) => $value !== null) === []; } - public function jsonSerialize(): ContentSubgraphIdentity + /** + * @return array + */ + public function jsonSerialize(): array { return $this->wrappedContentSubgraph->jsonSerialize(); } diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php index 79a1427ec93..9934276c7f2 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php @@ -17,9 +17,10 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Feature\RootNodeCreation\RootNodeHandling; use Neos\ContentRepository\Core\NodeType\NodeTypeName; +use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; -use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId; +use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; /** * This is the most important read model of a content repository. @@ -46,16 +47,15 @@ * * @api */ -interface ContentSubgraphInterface extends \JsonSerializable +interface ContentSubgraphInterface { - /** - * Returns the subgraph's identity, i.e. the current perspective we look at content from, composed of - * * the content repository the subgraph belongs to - * * the ID of the content stream we are currently working in - * * the dimension space point we are currently looking at - * * the applied visibility constraints - */ - public function getIdentity(): ContentSubgraphIdentity; + public function getContentRepositoryId(): ContentRepositoryId; + + public function getWorkspaceName(): WorkspaceName; + + public function getDimensionSpacePoint(): DimensionSpacePoint; + + public function getVisibilityConstraints(): VisibilityConstraints; /** * Find a single node by its aggregate id @@ -204,5 +204,9 @@ public function retrieveNodePath(NodeAggregateId $nodeAggregateId): AbsoluteNode */ public function countNodes(): int; - public function jsonSerialize(): ContentSubgraphIdentity; + /** + * @deprecated will be removed before Neos 9 release + * @return array + */ + public function jsonSerialize(): array; } diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindOperation.php index 4b5c511b7e0..c3f5908b6e1 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindOperation.php @@ -201,8 +201,8 @@ protected function getEntryPoints(array $contextNodes): array foreach ($contextNodes as $contextNode) { assert($contextNode instanceof Node); $subgraph = $this->contentRepositoryRegistry->subgraphForNode($contextNode); - $subgraphIdentifier = md5($subgraph->getIdentity()->contentStreamId->value - . '@' . $contextNode->dimensionSpacePoint->toJson()); + $subgraphIdentifier = md5($subgraph->getWorkspaceName()->value + . '@' . $subgraph->getDimensionSpacePoint()->toJson()); if (!isset($entryPoints[(string) $subgraphIdentifier])) { $entryPoints[(string) $subgraphIdentifier] = [ 'subgraph' => $subgraph, From c597ad400cd57abec0af657590f1778049a08399 Mon Sep 17 00:00:00 2001 From: Marc Henry Schultz <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 16:15:44 +0200 Subject: [PATCH 203/214] TASK: Prefer `get_object_vars` instead of manually specifying array shape Co-authored-by: Bastian Waidelich --- .../Classes/SharedModel/Node/NodeAddress.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php b/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php index f95883cf239..ae8c6ceced3 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php @@ -97,11 +97,6 @@ public function toJson(): string public function jsonSerialize(): mixed { - return [ - 'contentRepositoryId' => $this->contentRepositoryId, - 'workspaceName' => $this->workspaceName, - 'dimensionSpacePoint' => $this->dimensionSpacePoint, - 'aggregateId' => $this->aggregateId - ]; + return get_object_vars($this); } } From a190f3b5dec57ca1ed5c3032e62e6fee07c59d3e Mon Sep 17 00:00:00 2001 From: Marc Henry Schultz <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 17:05:41 +0200 Subject: [PATCH 204/214] TASK: Add speaking error to `NodeAddress->toJson` --- .../Classes/SharedModel/Node/NodeAddress.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php b/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php index ae8c6ceced3..d085a58686c 100644 --- a/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php +++ b/Neos.ContentRepository.Core/Classes/SharedModel/Node/NodeAddress.php @@ -92,7 +92,11 @@ public function equals(self $other): bool public function toJson(): string { - return json_encode($this, JSON_THROW_ON_ERROR); + try { + return json_encode($this, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new \RuntimeException(sprintf('Failed to JSON-encode NodeAddress: %s', $e->getMessage()), 1715608338, $e); + } } public function jsonSerialize(): mixed From 4ab3ec98d494b92e31572cd94a49c67890fc36b9 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 17:21:43 +0200 Subject: [PATCH 205/214] TASK: Adjust to changes on 9.0 --- .../src/Domain/Repository/ContentGraph.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php index 611afd7c761..54f5799f5ea 100644 --- a/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php +++ b/Neos.ContentGraph.DoctrineDbalAdapter/src/Domain/Repository/ContentGraph.php @@ -337,6 +337,7 @@ private function mapQueryBuilderToNodeAggregate(QueryBuilder $queryBuilder): ?No { return $this->nodeFactory->mapNodeRowsToNodeAggregate( $this->fetchRows($queryBuilder), + $this->workspaceName, $this->contentStreamId, VisibilityConstraints::withoutRestrictions() ); From 4e5993ac69ada51d4a450915f252e600776c22a3 Mon Sep 17 00:00:00 2001 From: Marc Henry Schultz <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 17:35:43 +0200 Subject: [PATCH 206/214] TASK: Readd missing `extends \JsonSerializable` --- .../Projection/ContentGraph/ContentSubgraphInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php index 9934276c7f2..083e85cdcf2 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/ContentSubgraphInterface.php @@ -47,7 +47,7 @@ * * @api */ -interface ContentSubgraphInterface +interface ContentSubgraphInterface extends \JsonSerializable { public function getContentRepositoryId(): ContentRepositoryId; From 0fb48aba4b7f7d9a922f721259f1842ad0a9385b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 13 May 2024 20:09:19 +0200 Subject: [PATCH 207/214] TASK: Merge `NodeLabelRenderer` and `NodeLabelRendererInterface` into one --- .../Classes/Projection/ContentGraph/Node.php | 6 ++-- .../FlowQueryOperations/LabelOperation.php | 6 ++-- ...er.php => DelegatingNodeLabelRenderer.php} | 33 ++++++++++++++----- .../ExpressionBasedNodeLabelGenerator.php | 2 ++ .../NodeLabel/NodeLabelGeneratorInterface.php | 4 +-- .../NodeLabel/NodeLabelRendererInterface.php | 13 -------- .../DimensionsMenuItemsImplementation.php | 6 ++-- .../Fusion/MenuItemsImplementation.php | 8 ++--- .../Utility/NodeUriPathSegmentGenerator.php | 6 ++-- .../ViewHelpers/Link/NodeViewHelper.php | 8 ++--- Neos.Neos/Configuration/Objects.yaml | 3 ++ .../References/NodeTypeDefinition.rst | 2 +- 12 files changed, 52 insertions(+), 45 deletions(-) rename Neos.Neos/Classes/Domain/NodeLabel/{NodeLabelRenderer.php => DelegatingNodeLabelRenderer.php} (60%) delete mode 100644 Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRendererInterface.php diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index 18506bd55ca..02b84ecee3e 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -195,7 +195,7 @@ public function hasProperty(string $propertyName): bool /** * Returned the node label as generated by the configured node label generator. * - * In PHP please use Neos' {@see NodeLabelRendererInterface} instead. + * In PHP please use Neos' {@see NodeLabelGeneratorInterface} instead. * * For Fusion please use the FlowQuery operation: * ``` @@ -206,12 +206,12 @@ public function hasProperty(string $propertyName): bool */ public function getLabel(): string { - if (!class_exists(\Neos\Neos\Domain\NodeLabel\NodeLabelRenderer::class)) { + if (!class_exists(\Neos\Neos\Domain\NodeLabel\DelegatingNodeLabelRenderer::class)) { throw new \BadMethodCallException('node labels are removed from standalone cr.'); } // highly illegal /** @phpstan-ignore-next-line */ - return (new \Neos\Neos\Domain\NodeLabel\NodeLabelRenderer())->renderNodeLabel($this)->value; + return (new \Neos\Neos\Domain\NodeLabel\DelegatingNodeLabelRenderer())->getLabel($this)->value; } /** diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php index 3ee887a90eb..2e521fa2be4 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php @@ -16,7 +16,7 @@ use Neos\Eel\FlowQuery\FlowQueryException; use Neos\Eel\FlowQuery\Operations\AbstractOperation; use Neos\Flow\Annotations as Flow; -use Neos\Neos\Domain\NodeLabel\NodeLabelRendererInterface; +use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; /** * Used to access the Node's label of a ContentRepository Node. @@ -45,7 +45,7 @@ class LabelOperation extends AbstractOperation protected static $final = true; #[Flow\Inject()] - protected NodeLabelRendererInterface $nodeLabelRenderer; + protected NodeLabelGeneratorInterface $nodeLabelGenerator; /** * {@inheritdoc} @@ -79,6 +79,6 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) if (!$node instanceof Node) { return null; } - return $this->nodeLabelRenderer->renderNodeLabel($node)->value; + return $this->nodeLabelGenerator->getLabel($node)->value; } } diff --git a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php b/Neos.Neos/Classes/Domain/NodeLabel/DelegatingNodeLabelRenderer.php similarity index 60% rename from Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php rename to Neos.Neos/Classes/Domain/NodeLabel/DelegatingNodeLabelRenderer.php index 9d6c59e363b..70759ab150a 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRenderer.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/DelegatingNodeLabelRenderer.php @@ -12,10 +12,10 @@ use Neos\Neos\Domain\Service\NodeTypeNameFactory; /** - * @internal please reference the interface {@see NodeLabelRendererInterface} instead. + * @internal please reference the interface {@see NodeLabelGeneratorInterface} instead. */ #[Flow\Scope('singleton')] -final readonly class NodeLabelRenderer implements NodeLabelRendererInterface +final readonly class DelegatingNodeLabelRenderer implements NodeLabelGeneratorInterface { public function __construct( private ContentRepositoryRegistry $contentRepositoryRegistry, @@ -23,16 +23,21 @@ public function __construct( ) { } - public function renderNodeLabel(Node $node): NodeLabel + public function getLabel(Node $node): NodeLabel { - $nodeTypeManager = $this->contentRepositoryRegistry->get($node->subgraphIdentity->contentRepositoryId)->getNodeTypeManager(); + $nodeTypeManager = $this->contentRepositoryRegistry->get($node->contentRepositoryId)->getNodeTypeManager(); $nodeType = $nodeTypeManager->getNodeType($node->nodeTypeName) ?? $nodeTypeManager->getNodeType(NodeTypeNameFactory::forFallback()); - $generator = $this->getNodeLabelGeneratorForNodeType($nodeType); + $generator = $this->getDelegatedGenerator($nodeType); + if ($generator instanceof DelegatingNodeLabelRenderer) { + throw new \RuntimeException( + 'Recursion detected, cannot specify DelegatingNodeLabelRenderer as generatorClass for NodeLabel as this is the default.', 1715622960 + ); + } return $generator->getLabel($node); } - private function getNodeLabelGeneratorForNodeType(?NodeType $nodeType): NodeLabelGeneratorInterface + private function getDelegatedGenerator(?NodeType $nodeType): NodeLabelGeneratorInterface { if ($nodeType?->hasConfiguration('label.generatorClass')) { $nodeLabelGeneratorClassName = $nodeType->getConfiguration('label.generatorClass'); @@ -49,8 +54,20 @@ private function getNodeLabelGeneratorForNodeType(?NodeType $nodeType): NodeLabe $nodeLabelGenerator = $this->objectManager->get(ExpressionBasedNodeLabelGenerator::class); $nodeLabelGenerator->setExpression($nodeType->getConfiguration('label')); } else { - /** @var NodeLabelGeneratorInterface $nodeLabelGenerator */ - $nodeLabelGenerator = $this->objectManager->get(NodeLabelGeneratorInterface::class); + $nodeLabelGenerator = new class implements NodeLabelGeneratorInterface { + public function getLabel(Node $node): NodeLabel + { + return NodeLabel::fromString( + sprintf( + '%s %s', + $node->nodeTypeName->value, + $node->name + ? sprintf('(%s)', $node->name->value) + : '' + ) + ); + } + }; } return $nodeLabelGenerator; diff --git a/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php index 337cc62f10b..d4c0da96451 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php @@ -19,6 +19,8 @@ /** * The expression based node label generator that is used as default if a label expression is configured. + * + * @internal please reference the interface {@see NodeLabelGeneratorInterface} instead. */ class ExpressionBasedNodeLabelGenerator implements NodeLabelGeneratorInterface { diff --git a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelGeneratorInterface.php b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelGeneratorInterface.php index 7ac36ead91b..c360e11b38d 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelGeneratorInterface.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelGeneratorInterface.php @@ -17,9 +17,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; /** - * Interface for rendering a node label string based on some strategy - * - * @api + * @api to access the Node's label in PHP, in Fusion one can use ${q(node).label()}. */ interface NodeLabelGeneratorInterface { diff --git a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRendererInterface.php b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRendererInterface.php deleted file mode 100644 index 390052f849b..00000000000 --- a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabelRendererInterface.php +++ /dev/null @@ -1,13 +0,0 @@ - @@ -220,7 +220,7 @@ protected function determineLabel(?Node $variant = null, array $metadata = []): if ($this->getContentDimensionIdentifierToLimitTo()) { return $metadata[$this->getContentDimensionIdentifierToLimitTo()->value]['label'] ?: ''; } elseif ($variant) { - return $this->nodeLabelRenderer->renderNodeLabel($variant)->value ?: ''; + return $this->nodeLabelGenerator->getLabel($variant)->value ?: ''; } else { return array_reduce($metadata, function ($carry, $item) { return $carry . (empty($carry) ? '' : '-') . $item['label']; diff --git a/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php index 360756f7dd1..85c86b912f2 100644 --- a/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php @@ -26,7 +26,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\Flow\Annotations as Flow; use Neos\Fusion\Exception as FusionException; -use Neos\Neos\Domain\NodeLabel\NodeLabelRendererInterface; +use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; use Neos\Neos\Domain\Service\NodeTypeNameFactory; /** @@ -65,7 +65,7 @@ class MenuItemsImplementation extends AbstractMenuItemsImplementation protected ?NodeTypeCriteria $nodeTypeCriteria = null; #[Flow\Inject()] - protected NodeLabelRendererInterface $nodeLabelRenderer; + protected NodeLabelGeneratorInterface $nodeLabelGenerator; /** * The last navigation level which should be rendered. @@ -220,7 +220,7 @@ protected function buildMenuItemFromNode(Node $node): MenuItem return new MenuItem( $node, $this->isCalculateItemStatesEnabled() ? $this->calculateItemState($node) : null, - $this->nodeLabelRenderer->renderNodeLabel($node)->value, + $this->nodeLabelGenerator->getLabel($node)->value, 0, [], $this->buildUri($node) @@ -244,7 +244,7 @@ protected function buildMenuItemFromSubtree(Subtree $subtree, int $startLevel = return new MenuItem( $node, $this->isCalculateItemStatesEnabled() ? $this->calculateItemState($node) : null, - $this->nodeLabelRenderer->renderNodeLabel($node)->value, + $this->nodeLabelGenerator->getLabel($node)->value, $subtree->level + $startLevel, $children, $this->buildUri($node) diff --git a/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php b/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php index b3f87c895d5..be53c2f1ab0 100644 --- a/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php +++ b/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php @@ -18,7 +18,7 @@ use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\Flow\Annotations as Flow; use Neos\Flow\I18n\Locale; -use Neos\Neos\Domain\NodeLabel\NodeLabelRendererInterface; +use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; use Neos\Neos\Exception; use Neos\Neos\Service\TransliterationService; @@ -32,7 +32,7 @@ class NodeUriPathSegmentGenerator protected TransliterationService $transliterationService; #[Flow\Inject] - protected NodeLabelRendererInterface $nodeLabelRenderer; + protected NodeLabelGeneratorInterface $nodeLabelGenerator; /** * Generates a URI path segment for a given node taking its language dimension value into account @@ -44,7 +44,7 @@ public function generateUriPathSegment(?Node $node = null, ?string $text = null) { $language = null; if ($node) { - $text = $text ?: $this->nodeLabelRenderer->renderNodeLabel($node)->value ?: ($node->nodeName?->value ?? ''); + $text = $text ?: $this->nodeLabelGenerator->getLabel($node)->value ?: ($node->nodeName?->value ?? ''); $languageDimensionValue = $node->originDimensionSpacePoint->coordinates['language'] ?? null; if (!is_null($languageDimensionValue)) { $locale = new Locale($languageDimensionValue); diff --git a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php index 8bf0d9ca5c3..883f16ede22 100644 --- a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php @@ -20,7 +20,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; -use Neos\Neos\Domain\NodeLabel\NodeLabelRendererInterface; +use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; use Neos\Neos\Domain\Service\NodeTypeNameFactory; use Neos\Neos\FrontendRouting\NodeAddress; use Neos\Neos\FrontendRouting\NodeAddressFactory; @@ -152,9 +152,9 @@ class NodeViewHelper extends AbstractTagBasedViewHelper /** * @Flow\Inject - * @var NodeLabelRendererInterface + * @var NodeLabelGeneratorInterface */ - protected $nodeLabelRenderer; + protected $nodeLabelGenerator; /** * Initialize arguments @@ -347,7 +347,7 @@ public function render(): string $this->templateVariableContainer->remove($this->arguments['nodeVariableName']); if ($content === null && $resolvedNode !== null) { - $content = $this->nodeLabelRenderer->renderNodeLabel($resolvedNode)->value; + $content = $this->nodeLabelGenerator->getLabel($resolvedNode)->value; } $this->tag->setContent($content); diff --git a/Neos.Neos/Configuration/Objects.yaml b/Neos.Neos/Configuration/Objects.yaml index 56b7ac6e23e..be5cd60dd91 100644 --- a/Neos.Neos/Configuration/Objects.yaml +++ b/Neos.Neos/Configuration/Objects.yaml @@ -80,3 +80,6 @@ Neos\Neos\AssetUsage\GlobalAssetUsageService: arguments: 3: setting: Neos.Neos.assetUsage.contentRepositories + +Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface: + className: Neos\Neos\Domain\NodeLabel\DelegatingNodeLabelRenderer diff --git a/Neos.Neos/Documentation/References/NodeTypeDefinition.rst b/Neos.Neos/Documentation/References/NodeTypeDefinition.rst index a28157ef5d7..0af58d7631d 100644 --- a/Neos.Neos/Documentation/References/NodeTypeDefinition.rst +++ b/Neos.Neos/Documentation/References/NodeTypeDefinition.rst @@ -93,7 +93,7 @@ The following options are allowed for defining a NodeType: ``generatorClass`` Alternatively the class of a node label generator implementing - ``Neos\ContentRepository\Domain\Model\NodeLabelGeneratorInterface`` can be specified as a nested option. + ``Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface`` can be specified as a nested option. ``options`` Options for third party-code, the Content-Repository ignores those options but Neos or Packages may use this to adjust From c099e9fd4026bbb3a68ea7d2dc7a336e7cc8f7e0 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Tue, 14 May 2024 07:25:55 +0000 Subject: [PATCH 208/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/EelHelpersReference.rst | 2 +- .../Documentation/References/FlowQueryOperationReference.rst | 2 +- .../Documentation/References/Signals/ContentRepository.rst | 2 +- Neos.Neos/Documentation/References/Signals/Flow.rst | 2 +- Neos.Neos/Documentation/References/Signals/Media.rst | 2 +- Neos.Neos/Documentation/References/Signals/Neos.rst | 2 +- Neos.Neos/Documentation/References/Validators/Flow.rst | 2 +- Neos.Neos/Documentation/References/Validators/Media.rst | 2 +- Neos.Neos/Documentation/References/Validators/Party.rst | 2 +- .../Documentation/References/ViewHelpers/ContentRepository.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index a85ba878dac..d75d7ee2394 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-04-27 +The following reference was automatically generated from code on 2024-05-14 .. _`Neos Command Reference: NEOS.CONTENTREPOSITORY`: diff --git a/Neos.Neos/Documentation/References/EelHelpersReference.rst b/Neos.Neos/Documentation/References/EelHelpersReference.rst index b17f624d37d..d28b378377c 100644 --- a/Neos.Neos/Documentation/References/EelHelpersReference.rst +++ b/Neos.Neos/Documentation/References/EelHelpersReference.rst @@ -3,7 +3,7 @@ Eel Helpers Reference ===================== -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Eel Helpers Reference: Api`: diff --git a/Neos.Neos/Documentation/References/FlowQueryOperationReference.rst b/Neos.Neos/Documentation/References/FlowQueryOperationReference.rst index 62b66e787ee..6410a4733e1 100644 --- a/Neos.Neos/Documentation/References/FlowQueryOperationReference.rst +++ b/Neos.Neos/Documentation/References/FlowQueryOperationReference.rst @@ -3,7 +3,7 @@ FlowQuery Operation Reference ============================= -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`FlowQuery Operation Reference: add`: diff --git a/Neos.Neos/Documentation/References/Signals/ContentRepository.rst b/Neos.Neos/Documentation/References/Signals/ContentRepository.rst index 686504855ce..3f60af6ab21 100644 --- a/Neos.Neos/Documentation/References/Signals/ContentRepository.rst +++ b/Neos.Neos/Documentation/References/Signals/ContentRepository.rst @@ -3,7 +3,7 @@ Content Repository Signals Reference ==================================== -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Content Repository Signals Reference: Context (``Neos\ContentRepository\Domain\Service\Context``)`: diff --git a/Neos.Neos/Documentation/References/Signals/Flow.rst b/Neos.Neos/Documentation/References/Signals/Flow.rst index 233ed55fb2d..2dc0a9841bc 100644 --- a/Neos.Neos/Documentation/References/Signals/Flow.rst +++ b/Neos.Neos/Documentation/References/Signals/Flow.rst @@ -3,7 +3,7 @@ Flow Signals Reference ====================== -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Flow Signals Reference: AbstractAdvice (``Neos\Flow\Aop\Advice\AbstractAdvice``)`: diff --git a/Neos.Neos/Documentation/References/Signals/Media.rst b/Neos.Neos/Documentation/References/Signals/Media.rst index 571e8194713..f801a400272 100644 --- a/Neos.Neos/Documentation/References/Signals/Media.rst +++ b/Neos.Neos/Documentation/References/Signals/Media.rst @@ -3,7 +3,7 @@ Media Signals Reference ======================= -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Media Signals Reference: AssetCollectionController (``Neos\Media\Browser\Controller\AssetCollectionController``)`: diff --git a/Neos.Neos/Documentation/References/Signals/Neos.rst b/Neos.Neos/Documentation/References/Signals/Neos.rst index 16377e2c472..8d89bf893a4 100644 --- a/Neos.Neos/Documentation/References/Signals/Neos.rst +++ b/Neos.Neos/Documentation/References/Signals/Neos.rst @@ -3,7 +3,7 @@ Neos Signals Reference ====================== -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Neos Signals Reference: AbstractCreate (``Neos\Neos\Ui\Domain\Model\Changes\AbstractCreate``)`: diff --git a/Neos.Neos/Documentation/References/Validators/Flow.rst b/Neos.Neos/Documentation/References/Validators/Flow.rst index eb4e9a47c4a..8f9a200fb41 100644 --- a/Neos.Neos/Documentation/References/Validators/Flow.rst +++ b/Neos.Neos/Documentation/References/Validators/Flow.rst @@ -3,7 +3,7 @@ Flow Validator Reference ======================== -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Flow Validator Reference: AggregateBoundaryValidator`: diff --git a/Neos.Neos/Documentation/References/Validators/Media.rst b/Neos.Neos/Documentation/References/Validators/Media.rst index e6b879874d6..ceef5c00666 100644 --- a/Neos.Neos/Documentation/References/Validators/Media.rst +++ b/Neos.Neos/Documentation/References/Validators/Media.rst @@ -3,7 +3,7 @@ Media Validator Reference ========================= -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Media Validator Reference: ImageOrientationValidator`: diff --git a/Neos.Neos/Documentation/References/Validators/Party.rst b/Neos.Neos/Documentation/References/Validators/Party.rst index 35039ba2e61..063fb4adebd 100644 --- a/Neos.Neos/Documentation/References/Validators/Party.rst +++ b/Neos.Neos/Documentation/References/Validators/Party.rst @@ -3,7 +3,7 @@ Party Validator Reference ========================= -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Party Validator Reference: AimAddressValidator`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/ContentRepository.rst b/Neos.Neos/Documentation/References/ViewHelpers/ContentRepository.rst index e88beee8763..1d73d3ac1f3 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/ContentRepository.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/ContentRepository.rst @@ -3,7 +3,7 @@ Content Repository ViewHelper Reference ####################################### -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Content Repository ViewHelper Reference: PaginateViewHelper`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index 35c85e5d6d0..291f60020d1 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index e732295f38b..c691f540bb9 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst b/Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst index 6e1785ab9ca..548020a520d 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Fusion.rst @@ -3,7 +3,7 @@ Fusion ViewHelper Reference ########################### -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Fusion ViewHelper Reference: fusion:render`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index 598bafa2a24..15f0ec2cee4 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index 66e26ba96b6..3db470f6ecb 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index 216935baa51..0813226bcde 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-04-27 +This reference was automatically generated from code on 2024-05-14 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From c9ed7be47435f8fc285a6918324fa67b22b7caf1 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 14 May 2024 10:46:33 +0200 Subject: [PATCH 209/214] TASK: Remove `NodeLabel` dto again --- .../Classes/Projection/ContentGraph/Node.php | 2 +- .../FlowQueryOperations/LabelOperation.php | 2 +- .../NodeLabel/DelegatingNodeLabelRenderer.php | 18 ++++++++---------- .../ExpressionBasedNodeLabelGenerator.php | 6 +++--- .../Classes/Domain/NodeLabel/NodeLabel.php | 18 ------------------ .../NodeLabel/NodeLabelGeneratorInterface.php | 2 +- .../DimensionsMenuItemsImplementation.php | 2 +- .../Classes/Fusion/MenuItemsImplementation.php | 4 ++-- .../Utility/NodeUriPathSegmentGenerator.php | 2 +- .../ViewHelpers/Link/NodeViewHelper.php | 2 +- 10 files changed, 19 insertions(+), 39 deletions(-) delete mode 100644 Neos.Neos/Classes/Domain/NodeLabel/NodeLabel.php diff --git a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php index 02b84ecee3e..37edc50e81c 100644 --- a/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php +++ b/Neos.ContentRepository.Core/Classes/Projection/ContentGraph/Node.php @@ -211,7 +211,7 @@ public function getLabel(): string } // highly illegal /** @phpstan-ignore-next-line */ - return (new \Neos\Neos\Domain\NodeLabel\DelegatingNodeLabelRenderer())->getLabel($this)->value; + return (new \Neos\Neos\Domain\NodeLabel\DelegatingNodeLabelRenderer())->getLabel($this); } /** diff --git a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php index 2e521fa2be4..37e4a02df83 100644 --- a/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php +++ b/Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/LabelOperation.php @@ -79,6 +79,6 @@ public function evaluate(FlowQuery $flowQuery, array $arguments) if (!$node instanceof Node) { return null; } - return $this->nodeLabelGenerator->getLabel($node)->value; + return $this->nodeLabelGenerator->getLabel($node); } } diff --git a/Neos.Neos/Classes/Domain/NodeLabel/DelegatingNodeLabelRenderer.php b/Neos.Neos/Classes/Domain/NodeLabel/DelegatingNodeLabelRenderer.php index 70759ab150a..b8a45a6bede 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/DelegatingNodeLabelRenderer.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/DelegatingNodeLabelRenderer.php @@ -23,7 +23,7 @@ public function __construct( ) { } - public function getLabel(Node $node): NodeLabel + public function getLabel(Node $node): string { $nodeTypeManager = $this->contentRepositoryRegistry->get($node->contentRepositoryId)->getNodeTypeManager(); $nodeType = $nodeTypeManager->getNodeType($node->nodeTypeName) @@ -55,16 +55,14 @@ private function getDelegatedGenerator(?NodeType $nodeType): NodeLabelGeneratorI $nodeLabelGenerator->setExpression($nodeType->getConfiguration('label')); } else { $nodeLabelGenerator = new class implements NodeLabelGeneratorInterface { - public function getLabel(Node $node): NodeLabel + public function getLabel(Node $node): string { - return NodeLabel::fromString( - sprintf( - '%s %s', - $node->nodeTypeName->value, - $node->name - ? sprintf('(%s)', $node->name->value) - : '' - ) + return sprintf( + '%s %s', + $node->nodeTypeName->value, + $node->name + ? sprintf('(%s)', $node->name->value) + : '' ); } }; diff --git a/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php index d4c0da96451..8c7010230e4 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/ExpressionBasedNodeLabelGenerator.php @@ -59,12 +59,12 @@ public function setExpression(string $expression): void * Render a node label based on an eel expression or return the expression if it is not valid eel. * @throws \Neos\Eel\Exception */ - public function getLabel(Node $node): NodeLabel + public function getLabel(Node $node): string { if (Utility::parseEelExpression($this->getExpression()) === null) { - return NodeLabel::fromString($this->getExpression()); + return $this->getExpression(); } $value = Utility::evaluateEelExpression($this->getExpression(), $this->eelEvaluator, ['node' => $node], $this->defaultContextConfiguration); - return NodeLabel::fromString((string)$value); + return (string)$value; } } diff --git a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabel.php b/Neos.Neos/Classes/Domain/NodeLabel/NodeLabel.php deleted file mode 100644 index 372fd5de5d0..00000000000 --- a/Neos.Neos/Classes/Domain/NodeLabel/NodeLabel.php +++ /dev/null @@ -1,18 +0,0 @@ -getContentDimensionIdentifierToLimitTo()) { return $metadata[$this->getContentDimensionIdentifierToLimitTo()->value]['label'] ?: ''; } elseif ($variant) { - return $this->nodeLabelGenerator->getLabel($variant)->value ?: ''; + return $this->nodeLabelGenerator->getLabel($variant) ?: ''; } else { return array_reduce($metadata, function ($carry, $item) { return $carry . (empty($carry) ? '' : '-') . $item['label']; diff --git a/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php b/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php index 85c86b912f2..9cf4abc075a 100644 --- a/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php +++ b/Neos.Neos/Classes/Fusion/MenuItemsImplementation.php @@ -220,7 +220,7 @@ protected function buildMenuItemFromNode(Node $node): MenuItem return new MenuItem( $node, $this->isCalculateItemStatesEnabled() ? $this->calculateItemState($node) : null, - $this->nodeLabelGenerator->getLabel($node)->value, + $this->nodeLabelGenerator->getLabel($node), 0, [], $this->buildUri($node) @@ -244,7 +244,7 @@ protected function buildMenuItemFromSubtree(Subtree $subtree, int $startLevel = return new MenuItem( $node, $this->isCalculateItemStatesEnabled() ? $this->calculateItemState($node) : null, - $this->nodeLabelGenerator->getLabel($node)->value, + $this->nodeLabelGenerator->getLabel($node), $subtree->level + $startLevel, $children, $this->buildUri($node) diff --git a/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php b/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php index be53c2f1ab0..e80e4bc947c 100644 --- a/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php +++ b/Neos.Neos/Classes/Utility/NodeUriPathSegmentGenerator.php @@ -44,7 +44,7 @@ public function generateUriPathSegment(?Node $node = null, ?string $text = null) { $language = null; if ($node) { - $text = $text ?: $this->nodeLabelGenerator->getLabel($node)->value ?: ($node->nodeName?->value ?? ''); + $text = $text ?: $this->nodeLabelGenerator->getLabel($node) ?: ($node->nodeName?->value ?? ''); $languageDimensionValue = $node->originDimensionSpacePoint->coordinates['language'] ?? null; if (!is_null($languageDimensionValue)) { $locale = new Locale($languageDimensionValue); diff --git a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php index 883f16ede22..a466b777065 100644 --- a/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php +++ b/Neos.Neos/Classes/ViewHelpers/Link/NodeViewHelper.php @@ -347,7 +347,7 @@ public function render(): string $this->templateVariableContainer->remove($this->arguments['nodeVariableName']); if ($content === null && $resolvedNode !== null) { - $content = $this->nodeLabelGenerator->getLabel($resolvedNode)->value; + $content = $this->nodeLabelGenerator->getLabel($resolvedNode); } $this->tag->setContent($content); From 523234011ae7ea519caf32249edbf8f5baee7043 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Tue, 14 May 2024 09:59:02 +0000 Subject: [PATCH 210/214] TASK: Update references [skip ci] --- Neos.Neos/Documentation/References/CommandReference.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Form.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Media.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/Neos.rst | 2 +- Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neos.Neos/Documentation/References/CommandReference.rst b/Neos.Neos/Documentation/References/CommandReference.rst index 961fd7cfd57..1e3b24895d5 100644 --- a/Neos.Neos/Documentation/References/CommandReference.rst +++ b/Neos.Neos/Documentation/References/CommandReference.rst @@ -19,7 +19,7 @@ commands that may be available, use:: ./flow help -The following reference was automatically generated from code on 2024-05-13 +The following reference was automatically generated from code on 2024-05-14 .. _`Neos Command Reference: NEOS.FLOW`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst index 0eca090ca26..291f60020d1 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/FluidAdaptor.rst @@ -3,7 +3,7 @@ FluidAdaptor ViewHelper Reference ################################# -This reference was automatically generated from code on 2024-05-13 +This reference was automatically generated from code on 2024-05-14 .. _`FluidAdaptor ViewHelper Reference: f:debug`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst index 112048a38ef..c691f540bb9 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Form.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Form.rst @@ -3,7 +3,7 @@ Form ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-05-13 +This reference was automatically generated from code on 2024-05-14 .. _`Form ViewHelper Reference: neos.form:form`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst index bdb546f8804..15f0ec2cee4 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Media.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Media.rst @@ -3,7 +3,7 @@ Media ViewHelper Reference ########################## -This reference was automatically generated from code on 2024-05-13 +This reference was automatically generated from code on 2024-05-14 .. _`Media ViewHelper Reference: neos.media:fileTypeIcon`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst index 4b384a5911a..0809cd7250a 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/Neos.rst @@ -3,7 +3,7 @@ Neos ViewHelper Reference ######################### -This reference was automatically generated from code on 2024-05-13 +This reference was automatically generated from code on 2024-05-14 .. _`Neos ViewHelper Reference: neos:backend.authenticationProviderLabel`: diff --git a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst index aee59c26eb9..400adbd9f86 100644 --- a/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst +++ b/Neos.Neos/Documentation/References/ViewHelpers/TYPO3Fluid.rst @@ -3,7 +3,7 @@ TYPO3 Fluid ViewHelper Reference ################################ -This reference was automatically generated from code on 2024-05-13 +This reference was automatically generated from code on 2024-05-14 .. _`TYPO3 Fluid ViewHelper Reference: f:alias`: From 4f6ed01385dcb45a0fb46856bc50a1165567ead0 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 14 May 2024 15:56:44 +0200 Subject: [PATCH 211/214] TASK: Fix phpcs error --- .../Classes/Domain/NodeLabel/DelegatingNodeLabelRenderer.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Neos.Neos/Classes/Domain/NodeLabel/DelegatingNodeLabelRenderer.php b/Neos.Neos/Classes/Domain/NodeLabel/DelegatingNodeLabelRenderer.php index b8a45a6bede..57f774c4003 100644 --- a/Neos.Neos/Classes/Domain/NodeLabel/DelegatingNodeLabelRenderer.php +++ b/Neos.Neos/Classes/Domain/NodeLabel/DelegatingNodeLabelRenderer.php @@ -31,7 +31,8 @@ public function getLabel(Node $node): string $generator = $this->getDelegatedGenerator($nodeType); if ($generator instanceof DelegatingNodeLabelRenderer) { throw new \RuntimeException( - 'Recursion detected, cannot specify DelegatingNodeLabelRenderer as generatorClass for NodeLabel as this is the default.', 1715622960 + 'Recursion detected, cannot specify DelegatingNodeLabelRenderer as generatorClass for NodeLabel as this is the default.', + 1715622960 ); } return $generator->getLabel($node); From 7951e1b36cdae8cc06cffc37ea134658360792da Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 14 May 2024 12:17:00 +0200 Subject: [PATCH 212/214] TASK: Adjust Menu.feature to NodeLabel change --- Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature index eca3e893556..8d98bff3ebd 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature @@ -501,13 +501,13 @@ Feature: Tests for the "Neos.Neos:Menu" and related Fusion prototypes """html
  • - Neos.Neos:Test.DocumentType1 + Neos.Neos:Test.DocumentType1 (a1)
  • From a8741f9108bdbdbf6dd4125c8f97ad08a79f5cf3 Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Tue, 14 May 2024 17:03:53 +0200 Subject: [PATCH 213/214] BUGFIX: Fix broken XLIFF files See neos/neos-development-collection#4921 --- .../Resources/Private/Translations/ar/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/cs/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/da/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/el/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/es/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/fi/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/fr/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/hu/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/id_ID/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/it/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/ja/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/km/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/lv/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/nl/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/no/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/pl/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/pt_BR/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/ru/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/sr/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/sv/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/tl_PH/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/tr/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/uk/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/vi/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/zh/NodeTypes/Document.xlf | 2 +- .../Resources/Private/Translations/zh_TW/NodeTypes/Document.xlf | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Neos.Neos/Resources/Private/Translations/ar/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/ar/NodeTypes/Document.xlf index d7c188cdda8..4c493915247 100644 --- a/Neos.Neos/Resources/Private/Translations/ar/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/ar/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - إخفاء في القوائم + إخفاء في القوائم diff --git a/Neos.Neos/Resources/Private/Translations/cs/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/cs/NodeTypes/Document.xlf index 0bd687cc256..c05f25c505a 100644 --- a/Neos.Neos/Resources/Private/Translations/cs/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/cs/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Skrýt v menu + Skrýt v menu diff --git a/Neos.Neos/Resources/Private/Translations/da/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/da/NodeTypes/Document.xlf index 8269a2f251b..21977d7fe28 100644 --- a/Neos.Neos/Resources/Private/Translations/da/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/da/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Skjul i menuer + Skjul i menuer diff --git a/Neos.Neos/Resources/Private/Translations/el/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/el/NodeTypes/Document.xlf index aab2340d833..01ae4993918 100644 --- a/Neos.Neos/Resources/Private/Translations/el/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/el/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Κρυφό στα μενού + Κρυφό στα μενού diff --git a/Neos.Neos/Resources/Private/Translations/es/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/es/NodeTypes/Document.xlf index 6dc1a90da53..d2ec8a13d3a 100644 --- a/Neos.Neos/Resources/Private/Translations/es/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/es/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Ocultar en los menús + Ocultar en los menús diff --git a/Neos.Neos/Resources/Private/Translations/fi/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/fi/NodeTypes/Document.xlf index a90b3c282c6..84e328e588f 100644 --- a/Neos.Neos/Resources/Private/Translations/fi/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/fi/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Piilota valikoissa + Piilota valikoissa diff --git a/Neos.Neos/Resources/Private/Translations/fr/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/fr/NodeTypes/Document.xlf index fca65146bd8..025219dae0d 100644 --- a/Neos.Neos/Resources/Private/Translations/fr/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/fr/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Cacher dans les menus + Cacher dans les menus diff --git a/Neos.Neos/Resources/Private/Translations/hu/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/hu/NodeTypes/Document.xlf index da3b51d122f..01f8e1ed7f0 100644 --- a/Neos.Neos/Resources/Private/Translations/hu/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/hu/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Elrejtés a menüben + Elrejtés a menüben diff --git a/Neos.Neos/Resources/Private/Translations/id_ID/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/id_ID/NodeTypes/Document.xlf index e87286032d2..b11c6c53c5d 100644 --- a/Neos.Neos/Resources/Private/Translations/id_ID/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/id_ID/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Sembunyikan di Menu + Sembunyikan di Menu diff --git a/Neos.Neos/Resources/Private/Translations/it/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/it/NodeTypes/Document.xlf index 9908e9b9731..72c877f40c5 100644 --- a/Neos.Neos/Resources/Private/Translations/it/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/it/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Nascondi nei menu + Nascondi nei menu diff --git a/Neos.Neos/Resources/Private/Translations/ja/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/ja/NodeTypes/Document.xlf index 64e6babf5c3..bc1b54c0219 100644 --- a/Neos.Neos/Resources/Private/Translations/ja/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/ja/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - メニュー非表示 + メニュー非表示 diff --git a/Neos.Neos/Resources/Private/Translations/km/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/km/NodeTypes/Document.xlf index caf5a07c412..86eacfb29c4 100644 --- a/Neos.Neos/Resources/Private/Translations/km/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/km/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - មិនបង្ហាុញក្នុងមីនុយ + មិនបង្ហាុញក្នុងមីនុយ diff --git a/Neos.Neos/Resources/Private/Translations/lv/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/lv/NodeTypes/Document.xlf index bc4d85b6e30..c8b29397511 100644 --- a/Neos.Neos/Resources/Private/Translations/lv/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/lv/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Paslēpt izvēlnē + Paslēpt izvēlnē diff --git a/Neos.Neos/Resources/Private/Translations/nl/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/nl/NodeTypes/Document.xlf index ef3a840a539..ea6b753d8e5 100644 --- a/Neos.Neos/Resources/Private/Translations/nl/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/nl/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Verberg in menu's + Verberg in menu's diff --git a/Neos.Neos/Resources/Private/Translations/no/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/no/NodeTypes/Document.xlf index b61861affb8..07e25137155 100644 --- a/Neos.Neos/Resources/Private/Translations/no/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/no/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Skjul i menyer + Skjul i menyer diff --git a/Neos.Neos/Resources/Private/Translations/pl/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/pl/NodeTypes/Document.xlf index 9739b067486..221041f7295 100644 --- a/Neos.Neos/Resources/Private/Translations/pl/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/pl/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Ukryj w menu + Ukryj w menu diff --git a/Neos.Neos/Resources/Private/Translations/pt_BR/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/pt_BR/NodeTypes/Document.xlf index 51de93f4a7d..3d6166a6427 100644 --- a/Neos.Neos/Resources/Private/Translations/pt_BR/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/pt_BR/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Esconder em menus + Esconder em menus diff --git a/Neos.Neos/Resources/Private/Translations/ru/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/ru/NodeTypes/Document.xlf index 70ca1a9bbf5..b3b476c4c8f 100644 --- a/Neos.Neos/Resources/Private/Translations/ru/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/ru/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Скрыть в меню + Скрыть в меню diff --git a/Neos.Neos/Resources/Private/Translations/sr/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/sr/NodeTypes/Document.xlf index b61c828dfc0..6d0fac4e492 100644 --- a/Neos.Neos/Resources/Private/Translations/sr/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/sr/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Сакривено у менију + Сакривено у менију diff --git a/Neos.Neos/Resources/Private/Translations/sv/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/sv/NodeTypes/Document.xlf index e5d4c4476fd..65b1c3d9085 100644 --- a/Neos.Neos/Resources/Private/Translations/sv/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/sv/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Dölj i menyer + Dölj i menyer diff --git a/Neos.Neos/Resources/Private/Translations/tl_PH/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/tl_PH/NodeTypes/Document.xlf index 7dda1678a82..a552fa23dad 100644 --- a/Neos.Neos/Resources/Private/Translations/tl_PH/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/tl_PH/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Hide in menus + Hide in menus diff --git a/Neos.Neos/Resources/Private/Translations/tr/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/tr/NodeTypes/Document.xlf index 677ff9ab331..4f72b7b545d 100644 --- a/Neos.Neos/Resources/Private/Translations/tr/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/tr/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Menülerde gizle + Menülerde gizle diff --git a/Neos.Neos/Resources/Private/Translations/uk/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/uk/NodeTypes/Document.xlf index d1c6cd0c742..e056f2084b8 100644 --- a/Neos.Neos/Resources/Private/Translations/uk/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/uk/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Сховати до меню + Сховати до меню diff --git a/Neos.Neos/Resources/Private/Translations/vi/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/vi/NodeTypes/Document.xlf index 3781cc18f57..deb1ba9a6af 100644 --- a/Neos.Neos/Resources/Private/Translations/vi/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/vi/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - Ẩn trong menu + Ẩn trong menu diff --git a/Neos.Neos/Resources/Private/Translations/zh/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/zh/NodeTypes/Document.xlf index 8b8b80080d1..582aea1487d 100644 --- a/Neos.Neos/Resources/Private/Translations/zh/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/zh/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - 在菜单中隐藏 + 在菜单中隐藏 diff --git a/Neos.Neos/Resources/Private/Translations/zh_TW/NodeTypes/Document.xlf b/Neos.Neos/Resources/Private/Translations/zh_TW/NodeTypes/Document.xlf index 4189a772f39..b4a2f55d143 100644 --- a/Neos.Neos/Resources/Private/Translations/zh_TW/NodeTypes/Document.xlf +++ b/Neos.Neos/Resources/Private/Translations/zh_TW/NodeTypes/Document.xlf @@ -16,7 +16,7 @@ Hide in menus - 於選單中隱藏 + 於選單中隱藏 From fe8f4b2425d3c4dcb949467b1b88c0997286c238 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 14 May 2024 17:36:38 +0200 Subject: [PATCH 214/214] TASK: Adjust other Menu.feature to NodeLabel change --- Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature index 8d98bff3ebd..28ab06fe590 100644 --- a/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature +++ b/Neos.Neos/Tests/Behavior/Features/Fusion/Menu.feature @@ -529,13 +529,13 @@ Feature: Tests for the "Neos.Neos:Menu" and related Fusion prototypes """html """