From aaeb68a85a88d3eaf93acec08d3bfa839c9028a8 Mon Sep 17 00:00:00 2001 From: Wilhelm Behncke Date: Mon, 22 Apr 2024 12:04:30 +0200 Subject: [PATCH 001/110] TASK: Move `Conflicts` concept to `Application\Shared` namespace This moves the classes `ConflictsOccurred`, `Conflict`, `Conflicts`, `ConflictsBuilder`, `ReasonForConflict`, `TypeOfChange` and `IconLabel` into the `Application\Shared` namespace and adjusts all references accordingly. These classes are going to be reused by multiple command handlers. --- Classes/Application/{SyncWorkspace => Shared}/Conflict.php | 2 +- Classes/Application/{SyncWorkspace => Shared}/Conflicts.php | 2 +- .../{SyncWorkspace => Shared}/ConflictsBuilder.php | 2 +- .../{SyncWorkspace => Shared}/ConflictsOccurred.php | 2 +- Classes/Application/{SyncWorkspace => Shared}/IconLabel.php | 2 +- .../{SyncWorkspace => Shared}/ReasonForConflict.php | 2 +- .../Application/{SyncWorkspace => Shared}/TypeOfChange.php | 2 +- .../SyncWorkspace/SyncWorkspaceCommandHandler.php | 5 +++++ Classes/Controller/BackendServiceController.php | 2 +- 9 files changed, 13 insertions(+), 8 deletions(-) rename Classes/Application/{SyncWorkspace => Shared}/Conflict.php (94%) rename Classes/Application/{SyncWorkspace => Shared}/Conflicts.php (97%) rename Classes/Application/{SyncWorkspace => Shared}/ConflictsBuilder.php (99%) rename Classes/Application/{SyncWorkspace => Shared}/ConflictsOccurred.php (93%) rename Classes/Application/{SyncWorkspace => Shared}/IconLabel.php (93%) rename Classes/Application/{SyncWorkspace => Shared}/ReasonForConflict.php (91%) rename Classes/Application/{SyncWorkspace => Shared}/TypeOfChange.php (93%) diff --git a/Classes/Application/SyncWorkspace/Conflict.php b/Classes/Application/Shared/Conflict.php similarity index 94% rename from Classes/Application/SyncWorkspace/Conflict.php rename to Classes/Application/Shared/Conflict.php index 5f96024af0..3f77f0ebeb 100644 --- a/Classes/Application/SyncWorkspace/Conflict.php +++ b/Classes/Application/Shared/Conflict.php @@ -12,7 +12,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Application\SyncWorkspace; +namespace Neos\Neos\Ui\Application\Shared; use Neos\Flow\Annotations as Flow; diff --git a/Classes/Application/SyncWorkspace/Conflicts.php b/Classes/Application/Shared/Conflicts.php similarity index 97% rename from Classes/Application/SyncWorkspace/Conflicts.php rename to Classes/Application/Shared/Conflicts.php index e53c5ef9e3..c1a292ffd5 100644 --- a/Classes/Application/SyncWorkspace/Conflicts.php +++ b/Classes/Application/Shared/Conflicts.php @@ -12,7 +12,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Application\SyncWorkspace; +namespace Neos\Neos\Ui\Application\Shared; use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; diff --git a/Classes/Application/SyncWorkspace/ConflictsBuilder.php b/Classes/Application/Shared/ConflictsBuilder.php similarity index 99% rename from Classes/Application/SyncWorkspace/ConflictsBuilder.php rename to Classes/Application/Shared/ConflictsBuilder.php index 138ed415fc..a30267f3ca 100644 --- a/Classes/Application/SyncWorkspace/ConflictsBuilder.php +++ b/Classes/Application/Shared/ConflictsBuilder.php @@ -12,7 +12,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Application\SyncWorkspace; +namespace Neos\Neos\Ui\Application\Shared; use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\ContentRepository; diff --git a/Classes/Application/SyncWorkspace/ConflictsOccurred.php b/Classes/Application/Shared/ConflictsOccurred.php similarity index 93% rename from Classes/Application/SyncWorkspace/ConflictsOccurred.php rename to Classes/Application/Shared/ConflictsOccurred.php index e62b9f9029..6917e42a9d 100644 --- a/Classes/Application/SyncWorkspace/ConflictsOccurred.php +++ b/Classes/Application/Shared/ConflictsOccurred.php @@ -12,7 +12,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Application\SyncWorkspace; +namespace Neos\Neos\Ui\Application\Shared; /** * @internal for communication within the Neos UI only diff --git a/Classes/Application/SyncWorkspace/IconLabel.php b/Classes/Application/Shared/IconLabel.php similarity index 93% rename from Classes/Application/SyncWorkspace/IconLabel.php rename to Classes/Application/Shared/IconLabel.php index 2e085ced07..7617421b4b 100644 --- a/Classes/Application/SyncWorkspace/IconLabel.php +++ b/Classes/Application/Shared/IconLabel.php @@ -12,7 +12,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Application\SyncWorkspace; +namespace Neos\Neos\Ui\Application\Shared; use Neos\Flow\Annotations as Flow; diff --git a/Classes/Application/SyncWorkspace/ReasonForConflict.php b/Classes/Application/Shared/ReasonForConflict.php similarity index 91% rename from Classes/Application/SyncWorkspace/ReasonForConflict.php rename to Classes/Application/Shared/ReasonForConflict.php index ccaf361dde..3979852e28 100644 --- a/Classes/Application/SyncWorkspace/ReasonForConflict.php +++ b/Classes/Application/Shared/ReasonForConflict.php @@ -12,7 +12,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Application\SyncWorkspace; +namespace Neos\Neos\Ui\Application\Shared; /** * @internal for communication within the Neos UI only diff --git a/Classes/Application/SyncWorkspace/TypeOfChange.php b/Classes/Application/Shared/TypeOfChange.php similarity index 93% rename from Classes/Application/SyncWorkspace/TypeOfChange.php rename to Classes/Application/Shared/TypeOfChange.php index 379ba5f119..cd474d30a5 100644 --- a/Classes/Application/SyncWorkspace/TypeOfChange.php +++ b/Classes/Application/Shared/TypeOfChange.php @@ -12,7 +12,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Application\SyncWorkspace; +namespace Neos\Neos\Ui\Application\Shared; /** * @internal for communication within the Neos UI only diff --git a/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php b/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php index 834837747e..2006bb0117 100644 --- a/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php +++ b/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php @@ -19,6 +19,8 @@ use Neos\Flow\Annotations as Flow; use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; use Neos\Neos\Domain\Workspace\WorkspaceProvider; +use Neos\Neos\Ui\Application\Shared\Conflicts; +use Neos\Neos\Ui\Application\Shared\ConflictsOccurred; /** * The application layer level command handler to for rebasing the workspace @@ -37,6 +39,9 @@ final class SyncWorkspaceCommandHandler #[Flow\Inject] protected NodeLabelGeneratorInterface $nodeLabelGenerator; + /** + * @throws ConflictsOccurred + */ public function handle(SyncWorkspaceCommand $command): void { try { diff --git a/Classes/Controller/BackendServiceController.php b/Classes/Controller/BackendServiceController.php index 4454e852eb..fdb6b56c46 100644 --- a/Classes/Controller/BackendServiceController.php +++ b/Classes/Controller/BackendServiceController.php @@ -46,7 +46,7 @@ use Neos\Neos\Ui\Application\PublishChangesInSite; use Neos\Neos\Ui\Application\ReloadNodes\ReloadNodesQuery; use Neos\Neos\Ui\Application\ReloadNodes\ReloadNodesQueryHandler; -use Neos\Neos\Ui\Application\SyncWorkspace\ConflictsOccurred; +use Neos\Neos\Ui\Application\Shared\ConflictsOccurred; use Neos\Neos\Ui\Application\SyncWorkspace\SyncWorkspaceCommand; use Neos\Neos\Ui\Application\SyncWorkspace\SyncWorkspaceCommandHandler; use Neos\Neos\Ui\ContentRepository\Service\NeosUiNodeService; From 5ff99a84306a89fb6d948f33500d4899965b1503 Mon Sep 17 00:00:00 2001 From: Wilhelm Behncke Date: Mon, 22 Apr 2024 13:01:36 +0200 Subject: [PATCH 002/110] TASK: Add command handlers for all Publish-related commands This includes the following tasks; - Move `PublishChangesInDocument` -> `PublishChangesInDocument\PublishChangesInDocumentCommand` - Create `PublishChangesInDocumentCommandHandler` - Move `PublishChangesInSite` -> `PublishChangesInSite\PublishChangesInSiteCommand` - Create `PublishChangesInSiteCommandHandler` --- .../PublishChangesInDocumentCommand.php} | 7 +- ...PublishChangesInDocumentCommandHandler.php | 68 ++++++++++++++++++ .../PublishChangesInSiteCommand.php} | 7 +- .../PublishChangesInSiteCommandHandler.php | 46 ++++++++++++ .../Application/Shared/PublishSucceeded.php | 37 ++++++++++ .../Controller/BackendServiceController.php | 70 +++++++------------ 6 files changed, 184 insertions(+), 51 deletions(-) rename Classes/Application/{PublishChangesInDocument.php => PublishChangesInDocument/PublishChangesInDocumentCommand.php} (88%) create mode 100644 Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php rename Classes/Application/{PublishChangesInSite.php => PublishChangesInSite/PublishChangesInSiteCommand.php} (89%) create mode 100644 Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php create mode 100644 Classes/Application/Shared/PublishSucceeded.php diff --git a/Classes/Application/PublishChangesInDocument.php b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommand.php similarity index 88% rename from Classes/Application/PublishChangesInDocument.php rename to Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommand.php index 85069b6606..87d9762511 100644 --- a/Classes/Application/PublishChangesInDocument.php +++ b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommand.php @@ -12,7 +12,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Application; +namespace Neos\Neos\Ui\Application\PublishChangesInDocument; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; @@ -20,12 +20,13 @@ use Neos\Flow\Annotations as Flow; /** - * The application layer level command DTO to communicate publication of all changes recorded for a given document + * The application layer level command DTO to communicate publication of + * all changes recorded for a given document * * @internal for communication within the Neos UI only */ #[Flow\Proxy(false)] -final readonly class PublishChangesInDocument +final readonly class PublishChangesInDocumentCommand { public function __construct( public ContentRepositoryId $contentRepositoryId, diff --git a/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php new file mode 100644 index 0000000000..4b5f6bb267 --- /dev/null +++ b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php @@ -0,0 +1,68 @@ +workspaceProvider->provideForWorkspaceName( + $command->contentRepositoryId, + $command->workspaceName + ); + $publishingResult = $workspace->publishChangesInDocument($command->documentId); + + return new PublishSucceeded( + numberOfAffectedChanges: $publishingResult->numberOfPublishedChanges, + baseWorkspaceName: $workspace->getCurrentBaseWorkspaceName()?->value + ); + } catch (NodeAggregateCurrentlyDoesNotExist $e) { + throw new \RuntimeException( + $this->getLabel('NodeNotPublishedMissingParentNode'), + 1705053430, + $e + ); + } catch (NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint $e) { + throw new \RuntimeException( + $this->getLabel('NodeNotPublishedParentNodeNotInCurrentDimension'), + 1705053432, + $e + ); + } + } +} diff --git a/Classes/Application/PublishChangesInSite.php b/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommand.php similarity index 89% rename from Classes/Application/PublishChangesInSite.php rename to Classes/Application/PublishChangesInSite/PublishChangesInSiteCommand.php index f645520cf4..0f5b82abde 100644 --- a/Classes/Application/PublishChangesInSite.php +++ b/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommand.php @@ -12,7 +12,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Application; +namespace Neos\Neos\Ui\Application\PublishChangesInSite; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; @@ -20,12 +20,13 @@ use Neos\Flow\Annotations as Flow; /** - * The application layer level command DTO to communicate publication of all changes recorded for a given site + * The application layer level command DTO to communicate publication of + * all changes recorded for a given site * * @internal for communication within the Neos UI only */ #[Flow\Proxy(false)] -final readonly class PublishChangesInSite +final readonly class PublishChangesInSiteCommand { public function __construct( public ContentRepositoryId $contentRepositoryId, diff --git a/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php b/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php new file mode 100644 index 0000000000..63710ca355 --- /dev/null +++ b/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php @@ -0,0 +1,46 @@ +workspaceProvider->provideForWorkspaceName( + $command->contentRepositoryId, + $command->workspaceName + ); + $publishingResult = $workspace->publishChangesInSite($command->siteId); + + return new PublishSucceeded( + numberOfAffectedChanges: $publishingResult->numberOfPublishedChanges, + baseWorkspaceName: $workspace->getCurrentBaseWorkspaceName()?->value + ); + } +} diff --git a/Classes/Application/Shared/PublishSucceeded.php b/Classes/Application/Shared/PublishSucceeded.php new file mode 100644 index 0000000000..dd20220b2a --- /dev/null +++ b/Classes/Application/Shared/PublishSucceeded.php @@ -0,0 +1,37 @@ + get_object_vars($this) + ]; + } +} diff --git a/Classes/Controller/BackendServiceController.php b/Classes/Controller/BackendServiceController.php index fdb6b56c46..41bb1fbd67 100644 --- a/Classes/Controller/BackendServiceController.php +++ b/Classes/Controller/BackendServiceController.php @@ -19,8 +19,6 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Dto\RebaseErrorHandlingStrategy; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints; -use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateCurrentlyDoesNotExist; -use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Eel\FlowQuery\FlowQuery; @@ -42,8 +40,10 @@ use Neos\Neos\Ui\Application\DiscardAllChanges; use Neos\Neos\Ui\Application\DiscardChangesInDocument; use Neos\Neos\Ui\Application\DiscardChangesInSite; -use Neos\Neos\Ui\Application\PublishChangesInDocument; -use Neos\Neos\Ui\Application\PublishChangesInSite; +use Neos\Neos\Ui\Application\PublishChangesInDocument\PublishChangesInDocumentCommand; +use Neos\Neos\Ui\Application\PublishChangesInDocument\PublishChangesInDocumentCommandHandler; +use Neos\Neos\Ui\Application\PublishChangesInSite\PublishChangesInSiteCommand; +use Neos\Neos\Ui\Application\PublishChangesInSite\PublishChangesInSiteCommandHandler; use Neos\Neos\Ui\Application\ReloadNodes\ReloadNodesQuery; use Neos\Neos\Ui\Application\ReloadNodes\ReloadNodesQueryHandler; use Neos\Neos\Ui\Application\Shared\ConflictsOccurred; @@ -140,6 +140,18 @@ class BackendServiceController extends ActionController */ protected $workspaceProvider; + /** + * @Flow\Inject + * @var PublishChangesInSiteCommandHandler + */ + protected $publishChangesInSiteCommandHandler; + + /** + * @Flow\Inject + * @var PublishChangesInDocumentCommandHandler + */ + protected $publishChangesInDocumentCommandHandler; + /** * @Flow\Inject * @var SyncWorkspaceCommandHandler @@ -206,20 +218,12 @@ public function publishChangesInSiteAction(array $command): void $command['siteId'], $contentRepositoryId )->nodeAggregateId->value; - $command = PublishChangesInSite::fromArray($command); - $workspace = $this->workspaceProvider->provideForWorkspaceName( - $command->contentRepositoryId, - $command->workspaceName - ); - $publishingResult = $workspace - ->publishChangesInSite($command->siteId); + $command = PublishChangesInSiteCommand::fromArray($command); - $this->view->assign('value', [ - 'success' => [ - 'numberOfAffectedChanges' => $publishingResult->numberOfPublishedChanges, - 'baseWorkspaceName' => $workspace->getCurrentBaseWorkspaceName()?->value - ] - ]); + $result = $this->publishChangesInSiteCommandHandler + ->handle($command); + + $this->view->assign('value', $result); } catch (\Exception $e) { $this->view->assign('value', [ 'error' => [ @@ -247,36 +251,12 @@ public function publishChangesInDocumentAction(array $command): void $command['documentId'], $contentRepositoryId )->nodeAggregateId->value; - $command = PublishChangesInDocument::fromArray($command); + $command = PublishChangesInDocumentCommand::fromArray($command); - $contentRepositoryId = SiteDetectionResult::fromRequest($this->request->getHttpRequest())->contentRepositoryId; + $result = $this->publishChangesInDocumentCommandHandler + ->handle($command); - try { - $workspace = $this->workspaceProvider->provideForWorkspaceName( - $command->contentRepositoryId, - $command->workspaceName - ); - $publishingResult = $workspace->publishChangesInDocument($command->documentId); - - $this->view->assign('value', [ - 'success' => [ - 'numberOfAffectedChanges' => $publishingResult->numberOfPublishedChanges, - 'baseWorkspaceName' => $workspace->getCurrentBaseWorkspaceName()?->value - ] - ]); - } catch (NodeAggregateCurrentlyDoesNotExist $e) { - throw new \RuntimeException( - $this->getLabel('NodeNotPublishedMissingParentNode'), - 1705053430, - $e - ); - } catch (NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint $e) { - throw new \RuntimeException( - $this->getLabel('NodeNotPublishedParentNodeNotInCurrentDimension'), - 1705053432, - $e - ); - } + $this->view->assign('value', $result); } catch (\Exception $e) { $this->view->assign('value', [ 'error' => [ From 10c00989de7761d8fefcb46d01219a131707a486 Mon Sep 17 00:00:00 2001 From: Wilhelm Behncke Date: Mon, 22 Apr 2024 17:13:23 +0200 Subject: [PATCH 003/110] TASK: Ensure that all Publish-related command handlers throw `ConflictsOccurred` --- .../PublishChangesInDocumentCommand.php | 7 ++- ...PublishChangesInDocumentCommandHandler.php | 23 ++++++++++ .../PublishChangesInSiteCommand.php | 7 ++- .../PublishChangesInSiteCommandHandler.php | 45 ++++++++++++++----- .../Controller/BackendServiceController.php | 4 +- .../src/Endpoints/index.ts | 8 ++-- packages/neos-ui-sagas/src/Publish/index.ts | 5 ++- 7 files changed, 79 insertions(+), 20 deletions(-) diff --git a/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommand.php b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommand.php index 87d9762511..f5c06a6616 100644 --- a/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommand.php +++ b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommand.php @@ -14,6 +14,7 @@ namespace Neos\Neos\Ui\Application\PublishChangesInDocument; +use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -32,11 +33,12 @@ public function __construct( public ContentRepositoryId $contentRepositoryId, public WorkspaceName $workspaceName, public NodeAggregateId $documentId, + public ?DimensionSpacePoint $preferredDimensionSpacePoint, ) { } /** - * @param array $values + * @param array{contentRepositoryId:string,workspaceName:string,documentId:string,preferredDimensionSpacePoint?:array} $values */ public static function fromArray(array $values): self { @@ -44,6 +46,9 @@ public static function fromArray(array $values): self ContentRepositoryId::fromString($values['contentRepositoryId']), WorkspaceName::fromString($values['workspaceName']), NodeAggregateId::fromString($values['documentId']), + isset($values['preferredDimensionSpacePoint']) && !empty($values['preferredDimensionSpacePoint']) + ? DimensionSpacePoint::fromLegacyDimensionArray($values['preferredDimensionSpacePoint']) + : null, ); } } diff --git a/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php index 4b5f6bb267..4c3555cc3b 100644 --- a/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php +++ b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php @@ -14,10 +14,14 @@ namespace Neos\Neos\Ui\Application\PublishChangesInDocument; +use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateCurrentlyDoesNotExist; use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint; +use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Neos\Domain\Workspace\WorkspaceProvider; +use Neos\Neos\Ui\Application\Shared\Conflicts; +use Neos\Neos\Ui\Application\Shared\ConflictsOccurred; use Neos\Neos\Ui\Application\Shared\PublishSucceeded; use Neos\Neos\Ui\Controller\TranslationTrait; @@ -32,6 +36,9 @@ final class PublishChangesInDocumentCommandHandler { use TranslationTrait; + #[Flow\Inject] + protected ContentRepositoryRegistry $contentRepositoryRegistry; + #[Flow\Inject] protected WorkspaceProvider $workspaceProvider; @@ -63,6 +70,22 @@ public function handle(PublishChangesInDocumentCommand $command): PublishSucceed 1705053432, $e ); + } catch (WorkspaceRebaseFailed $e) { + $conflictsBuilder = Conflicts::builder( + contentRepository: $this->contentRepositoryRegistry + ->get($command->contentRepositoryId), + workspaceName: $command->workspaceName, + preferredDimensionSpacePoint: $command->preferredDimensionSpacePoint + ); + + foreach ($e->commandsThatFailedDuringRebase as $commandThatFailedDuringRebase) { + $conflictsBuilder->addCommandThatFailedDuringRebase($commandThatFailedDuringRebase); + } + + throw new ConflictsOccurred( + $conflictsBuilder->build(), + 1712832228 + ); } } } diff --git a/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommand.php b/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommand.php index 0f5b82abde..f177482c41 100644 --- a/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommand.php +++ b/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommand.php @@ -14,6 +14,7 @@ namespace Neos\Neos\Ui\Application\PublishChangesInSite; +use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; @@ -32,11 +33,12 @@ public function __construct( public ContentRepositoryId $contentRepositoryId, public WorkspaceName $workspaceName, public NodeAggregateId $siteId, + public ?DimensionSpacePoint $preferredDimensionSpacePoint, ) { } /** - * @param array $values + * @param array{contentRepositoryId:string,workspaceName:string,siteId:string,preferredDimensionSpacePoint?:array} $values */ public static function fromArray(array $values): self { @@ -44,6 +46,9 @@ public static function fromArray(array $values): self ContentRepositoryId::fromString($values['contentRepositoryId']), WorkspaceName::fromString($values['workspaceName']), NodeAggregateId::fromString($values['siteId']), + isset($values['preferredDimensionSpacePoint']) && !empty($values['preferredDimensionSpacePoint']) + ? DimensionSpacePoint::fromLegacyDimensionArray($values['preferredDimensionSpacePoint']) + : null, ); } } diff --git a/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php b/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php index 63710ca355..ad83969e8c 100644 --- a/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php +++ b/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php @@ -14,8 +14,12 @@ namespace Neos\Neos\Ui\Application\PublishChangesInSite; +use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed; +use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; use Neos\Neos\Domain\Workspace\WorkspaceProvider; +use Neos\Neos\Ui\Application\Shared\Conflicts; +use Neos\Neos\Ui\Application\Shared\ConflictsOccurred; use Neos\Neos\Ui\Application\Shared\PublishSucceeded; /** @@ -27,20 +31,41 @@ #[Flow\Scope("singleton")] final class PublishChangesInSiteCommandHandler { + #[Flow\Inject] + protected ContentRepositoryRegistry $contentRepositoryRegistry; + #[Flow\Inject] protected WorkspaceProvider $workspaceProvider; public function handle(PublishChangesInSiteCommand $command): PublishSucceeded { - $workspace = $this->workspaceProvider->provideForWorkspaceName( - $command->contentRepositoryId, - $command->workspaceName - ); - $publishingResult = $workspace->publishChangesInSite($command->siteId); - - return new PublishSucceeded( - numberOfAffectedChanges: $publishingResult->numberOfPublishedChanges, - baseWorkspaceName: $workspace->getCurrentBaseWorkspaceName()?->value - ); + try { + $workspace = $this->workspaceProvider->provideForWorkspaceName( + $command->contentRepositoryId, + $command->workspaceName + ); + $publishingResult = $workspace->publishChangesInSite($command->siteId); + + return new PublishSucceeded( + numberOfAffectedChanges: $publishingResult->numberOfPublishedChanges, + baseWorkspaceName: $workspace->getCurrentBaseWorkspaceName()?->value + ); + } catch (WorkspaceRebaseFailed $e) { + $conflictsBuilder = Conflicts::builder( + contentRepository: $this->contentRepositoryRegistry + ->get($command->contentRepositoryId), + workspaceName: $command->workspaceName, + preferredDimensionSpacePoint: $command->preferredDimensionSpacePoint + ); + + foreach ($e->commandsThatFailedDuringRebase as $commandThatFailedDuringRebase) { + $conflictsBuilder->addCommandThatFailedDuringRebase($commandThatFailedDuringRebase); + } + + throw new ConflictsOccurred( + $conflictsBuilder->build(), + 1712832228 + ); + } } } diff --git a/Classes/Controller/BackendServiceController.php b/Classes/Controller/BackendServiceController.php index 41bb1fbd67..37d08646da 100644 --- a/Classes/Controller/BackendServiceController.php +++ b/Classes/Controller/BackendServiceController.php @@ -206,7 +206,7 @@ public function changeAction(array $changes): void /** * Publish all changes in the current site * - * @phpstan-param array $command + * @phpstan-param array{workspaceName:string,siteId:string,preferredDimensionSpacePoint?:array} $command */ public function publishChangesInSiteAction(array $command): void { @@ -239,7 +239,7 @@ public function publishChangesInSiteAction(array $command): void /** * Publish all changes in the current document * - * @phpstan-param array $command + * @phpstan-param array{workspaceName:string,documentId:string,preferredDimensionSpacePoint?:array} $command */ public function publishChangesInDocumentAction(array $command): void { diff --git a/packages/neos-ui-backend-connector/src/Endpoints/index.ts b/packages/neos-ui-backend-connector/src/Endpoints/index.ts index 1bcfed2c08..aa2130b77d 100644 --- a/packages/neos-ui-backend-connector/src/Endpoints/index.ts +++ b/packages/neos-ui-backend-connector/src/Endpoints/index.ts @@ -69,7 +69,7 @@ export default (routes: Routes) => { })).then(response => fetchWithErrorHandling.parseJson(response)) .catch(reason => fetchWithErrorHandling.generalErrorHandler(reason)); - const publishChangesInSite = (siteId: NodeContextPath, workspaceName: WorkspaceName) => fetchWithErrorHandling.withCsrfToken(csrfToken => ({ + const publishChangesInSite = (siteId: NodeContextPath, workspaceName: WorkspaceName, preferredDimensionSpacePoint: null|DimensionCombination) => fetchWithErrorHandling.withCsrfToken(csrfToken => ({ url: routes.ui.service.publishChangesInSite, method: 'POST', credentials: 'include', @@ -78,12 +78,12 @@ export default (routes: Routes) => { 'Content-Type': 'application/json' }, body: JSON.stringify({ - command: {siteId, workspaceName} + command: {siteId, workspaceName, preferredDimensionSpacePoint} }) })).then(response => fetchWithErrorHandling.parseJson(response)) .catch(reason => fetchWithErrorHandling.generalErrorHandler(reason)); - const publishChangesInDocument = (documentId: NodeContextPath, workspaceName: WorkspaceName) => fetchWithErrorHandling.withCsrfToken(csrfToken => ({ + const publishChangesInDocument = (documentId: NodeContextPath, workspaceName: WorkspaceName, preferredDimensionSpacePoint: null|DimensionCombination) => fetchWithErrorHandling.withCsrfToken(csrfToken => ({ url: routes.ui.service.publishChangesInDocument, method: 'POST', credentials: 'include', @@ -92,7 +92,7 @@ export default (routes: Routes) => { 'Content-Type': 'application/json' }, body: JSON.stringify({ - command: {documentId, workspaceName} + command: {documentId, workspaceName, preferredDimensionSpacePoint} }) })).then(response => fetchWithErrorHandling.parseJson(response)) .catch(reason => fetchWithErrorHandling.generalErrorHandler(reason)); diff --git a/packages/neos-ui-sagas/src/Publish/index.ts b/packages/neos-ui-sagas/src/Publish/index.ts index 63220b1339..e552422347 100644 --- a/packages/neos-ui-sagas/src/Publish/index.ts +++ b/packages/neos-ui-sagas/src/Publish/index.ts @@ -10,7 +10,7 @@ import {put, call, select, takeEvery, take, race, all} from 'redux-saga/effects'; import {AnyError} from '@neos-project/neos-ui-error'; -import {NodeContextPath, WorkspaceName} from '@neos-project/neos-ts-interfaces'; +import {DimensionCombination, NodeContextPath, WorkspaceName} from '@neos-project/neos-ts-interfaces'; import {actionTypes, actions, selectors} from '@neos-project/neos-ui-redux-store'; import {GlobalState} from '@neos-project/neos-ui-redux-store/src/System'; import {FeedbackEnvelope} from '@neos-project/neos-ui-redux-store/src/ServerFeedback'; @@ -83,6 +83,7 @@ export function * watchPublishing({routes}: {routes: Routes}) { const {ancestorIdSelector} = SELECTORS_BY_SCOPE[scope]; const workspaceName: WorkspaceName = yield select(selectors.CR.Workspaces.personalWorkspaceNameSelector); + const dimensionSpacePoint: null|DimensionCombination = yield select(selectors.CR.ContentDimensions.active); const ancestorId: NodeContextPath = ancestorIdSelector ? yield select(ancestorIdSelector) : null; @@ -92,7 +93,7 @@ export function * watchPublishing({routes}: {routes: Routes}) { window.addEventListener('beforeunload', handleWindowBeforeUnload); const result: PublishingResponse = scope === PublishingScope.ALL ? yield call(endpoint as any, workspaceName) - : yield call(endpoint, ancestorId, workspaceName); + : yield call(endpoint, ancestorId, workspaceName, dimensionSpacePoint); if ('success' in result) { yield put(actions.CR.Publishing.succeed(result.success.numberOfAffectedChanges)); From 852bad1fb0dca94e47bedab03b9e89a9846689f5 Mon Sep 17 00:00:00 2001 From: Wilhelm Behncke Date: Mon, 22 Apr 2024 17:49:20 +0200 Subject: [PATCH 004/110] TASK: Turn `ConflictsOccurred` into a Result DTO rather than an exception --- ...PublishChangesInDocumentCommandHandler.php | 10 +++--- .../PublishChangesInSiteCommandHandler.php | 10 +++--- .../Application/Shared/ConflictsOccurred.php | 17 ++++++---- .../SyncWorkspaceCommandHandler.php | 15 ++++----- .../SyncWorkspace/SyncingSucceeded.php | 32 +++++++++++++++++++ .../Controller/BackendServiceController.php | 10 ++---- 6 files changed, 61 insertions(+), 33 deletions(-) create mode 100644 Classes/Application/SyncWorkspace/SyncingSucceeded.php diff --git a/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php index 4c3555cc3b..73511cd6b4 100644 --- a/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php +++ b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php @@ -45,8 +45,9 @@ final class PublishChangesInDocumentCommandHandler /** * @throws NodeAggregateCurrentlyDoesNotExist */ - public function handle(PublishChangesInDocumentCommand $command): PublishSucceeded - { + public function handle( + PublishChangesInDocumentCommand $command + ): PublishSucceeded|ConflictsOccurred { try { $workspace = $this->workspaceProvider->provideForWorkspaceName( $command->contentRepositoryId, @@ -82,9 +83,8 @@ public function handle(PublishChangesInDocumentCommand $command): PublishSucceed $conflictsBuilder->addCommandThatFailedDuringRebase($commandThatFailedDuringRebase); } - throw new ConflictsOccurred( - $conflictsBuilder->build(), - 1712832228 + return new ConflictsOccurred( + conflicts: $conflictsBuilder->build() ); } } diff --git a/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php b/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php index ad83969e8c..5b333c6d30 100644 --- a/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php +++ b/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php @@ -37,8 +37,9 @@ final class PublishChangesInSiteCommandHandler #[Flow\Inject] protected WorkspaceProvider $workspaceProvider; - public function handle(PublishChangesInSiteCommand $command): PublishSucceeded - { + public function handle( + PublishChangesInSiteCommand $command + ): PublishSucceeded|ConflictsOccurred { try { $workspace = $this->workspaceProvider->provideForWorkspaceName( $command->contentRepositoryId, @@ -62,9 +63,8 @@ public function handle(PublishChangesInSiteCommand $command): PublishSucceeded $conflictsBuilder->addCommandThatFailedDuringRebase($commandThatFailedDuringRebase); } - throw new ConflictsOccurred( - $conflictsBuilder->build(), - 1712832228 + return new ConflictsOccurred( + conflicts: $conflictsBuilder->build() ); } } diff --git a/Classes/Application/Shared/ConflictsOccurred.php b/Classes/Application/Shared/ConflictsOccurred.php index 6917e42a9d..fcf1817f5a 100644 --- a/Classes/Application/Shared/ConflictsOccurred.php +++ b/Classes/Application/Shared/ConflictsOccurred.php @@ -14,18 +14,21 @@ namespace Neos\Neos\Ui\Application\Shared; +use Neos\Flow\Annotations as Flow; + /** * @internal for communication within the Neos UI only */ -final class ConflictsOccurred extends \Exception +#[Flow\Proxy(false)] +final readonly class ConflictsOccurred implements \JsonSerializable { public function __construct( - public readonly Conflicts $conflicts, - int $code + public readonly Conflicts $conflicts ) { - parent::__construct( - sprintf('%s conflict(s) occurred during rebase.', count($conflicts)), - $code - ); + } + + public function jsonSerialize(): mixed + { + return get_object_vars($this); } } diff --git a/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php b/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php index 2006bb0117..1019caa643 100644 --- a/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php +++ b/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php @@ -39,11 +39,9 @@ final class SyncWorkspaceCommandHandler #[Flow\Inject] protected NodeLabelGeneratorInterface $nodeLabelGenerator; - /** - * @throws ConflictsOccurred - */ - public function handle(SyncWorkspaceCommand $command): void - { + public function handle( + SyncWorkspaceCommand $command + ): SyncingSucceeded|ConflictsOccurred { try { $workspace = $this->workspaceProvider->provideForWorkspaceName( $command->contentRepositoryId, @@ -51,6 +49,8 @@ public function handle(SyncWorkspaceCommand $command): void ); $workspace->rebase($command->rebaseErrorHandlingStrategy); + + return new SyncingSucceeded(); } catch (WorkspaceRebaseFailed $e) { $conflictsBuilder = Conflicts::builder( contentRepository: $this->contentRepositoryRegistry @@ -64,9 +64,8 @@ public function handle(SyncWorkspaceCommand $command): void $conflictsBuilder->addCommandThatFailedDuringRebase($commandThatFailedDuringRebase); } - throw new ConflictsOccurred( - $conflictsBuilder->build(), - 1712832228 + return new ConflictsOccurred( + conflicts: $conflictsBuilder->build() ); } } diff --git a/Classes/Application/SyncWorkspace/SyncingSucceeded.php b/Classes/Application/SyncWorkspace/SyncingSucceeded.php new file mode 100644 index 0000000000..52f5a0b288 --- /dev/null +++ b/Classes/Application/SyncWorkspace/SyncingSucceeded.php @@ -0,0 +1,32 @@ + true]; + } +} diff --git a/Classes/Controller/BackendServiceController.php b/Classes/Controller/BackendServiceController.php index 37d08646da..2b91bdbfce 100644 --- a/Classes/Controller/BackendServiceController.php +++ b/Classes/Controller/BackendServiceController.php @@ -747,15 +747,9 @@ public function syncWorkspaceAction(string $targetWorkspaceName, bool $force, ?a : RebaseErrorHandlingStrategy::STRATEGY_FAIL ); - $this->syncWorkspaceCommandHandler->handle($command); + $result = $this->syncWorkspaceCommandHandler->handle($command); - $this->view->assign('value', [ - 'success' => true - ]); - } catch (ConflictsOccurred $e) { - $this->view->assign('value', [ - 'conflicts' => $e->conflicts - ]); + $this->view->assign('value', $result); } catch (\Exception $e) { $this->view->assign('value', [ 'error' => [ From aec75b0f92df5cfaef8c4d5f2a1b19dadd0b402b Mon Sep 17 00:00:00 2001 From: Wilhelm Behncke Date: Tue, 23 Apr 2024 12:51:52 +0200 Subject: [PATCH 005/110] TASK: Move logic to create Conflicts from WorkspaceRebaseFailed into dedicated Factory --- ...PublishChangesInDocumentCommandHandler.php | 15 +- .../PublishChangesInSiteCommandHandler.php | 15 +- Classes/Application/Shared/Conflict.php | 2 + Classes/Application/Shared/Conflicts.php | 19 +- .../Application/Shared/ConflictsBuilder.php | 257 +--------------- .../SyncWorkspaceCommandHandler.php | 10 +- .../ContentRepository/ConflictsFactory.php | 281 ++++++++++++++++++ 7 files changed, 312 insertions(+), 287 deletions(-) create mode 100644 Classes/Infrastructure/ContentRepository/ConflictsFactory.php diff --git a/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php index 73511cd6b4..9e89d1993b 100644 --- a/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php +++ b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php @@ -19,11 +19,12 @@ use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; +use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; use Neos\Neos\Domain\Workspace\WorkspaceProvider; -use Neos\Neos\Ui\Application\Shared\Conflicts; use Neos\Neos\Ui\Application\Shared\ConflictsOccurred; use Neos\Neos\Ui\Application\Shared\PublishSucceeded; use Neos\Neos\Ui\Controller\TranslationTrait; +use Neos\Neos\Ui\Infrastructure\ContentRepository\ConflictsFactory; /** * The application layer level command handler to perform publication of @@ -42,6 +43,9 @@ final class PublishChangesInDocumentCommandHandler #[Flow\Inject] protected WorkspaceProvider $workspaceProvider; + #[Flow\Inject] + protected NodeLabelGeneratorInterface $nodeLabelGenerator; + /** * @throws NodeAggregateCurrentlyDoesNotExist */ @@ -72,19 +76,16 @@ public function handle( $e ); } catch (WorkspaceRebaseFailed $e) { - $conflictsBuilder = Conflicts::builder( + $conflictsFactory = new ConflictsFactory( contentRepository: $this->contentRepositoryRegistry ->get($command->contentRepositoryId), + nodeLabelGenerator: $this->nodeLabelGenerator, workspaceName: $command->workspaceName, preferredDimensionSpacePoint: $command->preferredDimensionSpacePoint ); - foreach ($e->commandsThatFailedDuringRebase as $commandThatFailedDuringRebase) { - $conflictsBuilder->addCommandThatFailedDuringRebase($commandThatFailedDuringRebase); - } - return new ConflictsOccurred( - conflicts: $conflictsBuilder->build() + conflicts: $conflictsFactory->fromWorkspaceRebaseFailed($e) ); } } diff --git a/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php b/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php index 5b333c6d30..b7d8a2e792 100644 --- a/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php +++ b/Classes/Application/PublishChangesInSite/PublishChangesInSiteCommandHandler.php @@ -17,10 +17,11 @@ use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed; use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; use Neos\Flow\Annotations as Flow; +use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; use Neos\Neos\Domain\Workspace\WorkspaceProvider; -use Neos\Neos\Ui\Application\Shared\Conflicts; use Neos\Neos\Ui\Application\Shared\ConflictsOccurred; use Neos\Neos\Ui\Application\Shared\PublishSucceeded; +use Neos\Neos\Ui\Infrastructure\ContentRepository\ConflictsFactory; /** * The application layer level command handler to perform publication of @@ -37,6 +38,9 @@ final class PublishChangesInSiteCommandHandler #[Flow\Inject] protected WorkspaceProvider $workspaceProvider; + #[Flow\Inject] + protected NodeLabelGeneratorInterface $nodeLabelGenerator; + public function handle( PublishChangesInSiteCommand $command ): PublishSucceeded|ConflictsOccurred { @@ -52,19 +56,16 @@ public function handle( baseWorkspaceName: $workspace->getCurrentBaseWorkspaceName()?->value ); } catch (WorkspaceRebaseFailed $e) { - $conflictsBuilder = Conflicts::builder( + $conflictsFactory = new ConflictsFactory( contentRepository: $this->contentRepositoryRegistry ->get($command->contentRepositoryId), + nodeLabelGenerator: $this->nodeLabelGenerator, workspaceName: $command->workspaceName, preferredDimensionSpacePoint: $command->preferredDimensionSpacePoint ); - foreach ($e->commandsThatFailedDuringRebase as $commandThatFailedDuringRebase) { - $conflictsBuilder->addCommandThatFailedDuringRebase($commandThatFailedDuringRebase); - } - return new ConflictsOccurred( - conflicts: $conflictsBuilder->build() + conflicts: $conflictsFactory->fromWorkspaceRebaseFailed($e) ); } } diff --git a/Classes/Application/Shared/Conflict.php b/Classes/Application/Shared/Conflict.php index 3f77f0ebeb..faf13b690a 100644 --- a/Classes/Application/Shared/Conflict.php +++ b/Classes/Application/Shared/Conflict.php @@ -14,6 +14,7 @@ namespace Neos\Neos\Ui\Application\Shared; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\Flow\Annotations as Flow; /** @@ -25,6 +26,7 @@ final readonly class Conflict implements \JsonSerializable { public function __construct( + public string $key, public ?IconLabel $affectedSite, public ?IconLabel $affectedDocument, public ?IconLabel $affectedNode, diff --git a/Classes/Application/Shared/Conflicts.php b/Classes/Application/Shared/Conflicts.php index c1a292ffd5..52bfa1723c 100644 --- a/Classes/Application/Shared/Conflicts.php +++ b/Classes/Application/Shared/Conflicts.php @@ -14,11 +14,7 @@ namespace Neos\Neos\Ui\Application\Shared; -use Neos\ContentRepository\Core\ContentRepository; -use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Flow\Annotations as Flow; -use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; /** * @internal for communication within the Neos UI only @@ -34,18 +30,9 @@ public function __construct(Conflict ...$items) $this->items = $items; } - public static function builder( - ContentRepository $contentRepository, - NodeLabelGeneratorInterface $nodeLabelGenerator, - WorkspaceName $workspaceName, - ?DimensionSpacePoint $preferredDimensionSpacePoint, - ): ConflictsBuilder { - return new ConflictsBuilder( - contentRepository: $contentRepository, - nodeLabelGenerator: $nodeLabelGenerator, - workspaceName: $workspaceName, - preferredDimensionSpacePoint: $preferredDimensionSpacePoint - ); + public static function builder(): ConflictsBuilder + { + return new ConflictsBuilder(); } public function jsonSerialize(): mixed diff --git a/Classes/Application/Shared/ConflictsBuilder.php b/Classes/Application/Shared/ConflictsBuilder.php index a30267f3ca..379b6a441a 100644 --- a/Classes/Application/Shared/ConflictsBuilder.php +++ b/Classes/Application/Shared/ConflictsBuilder.php @@ -14,37 +14,7 @@ namespace Neos\Neos\Ui\Application\Shared; -use Neos\ContentRepository\Core\CommandHandler\CommandInterface; -use Neos\ContentRepository\Core\ContentRepository; -use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; -use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; -use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNodeAndSerializedProperties; -use Neos\ContentRepository\Core\Feature\NodeDisabling\Command\DisableNodeAggregate; -use Neos\ContentRepository\Core\Feature\NodeDisabling\Command\EnableNodeAggregate; -use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetNodeProperties; -use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetSerializedNodeProperties; -use Neos\ContentRepository\Core\Feature\NodeMove\Command\MoveNodeAggregate; -use Neos\ContentRepository\Core\Feature\NodeReferencing\Command\SetNodeReferences; -use Neos\ContentRepository\Core\Feature\NodeReferencing\Command\SetSerializedNodeReferences; -use Neos\ContentRepository\Core\Feature\NodeRemoval\Command\RemoveNodeAggregate; -use Neos\ContentRepository\Core\Feature\NodeTypeChange\Command\ChangeNodeAggregateType; -use Neos\ContentRepository\Core\Feature\NodeVariation\Command\CreateNodeVariant; -use Neos\ContentRepository\Core\Feature\SubtreeTagging\Command\TagSubtree; -use Neos\ContentRepository\Core\Feature\SubtreeTagging\Command\UntagSubtree; -use Neos\ContentRepository\Core\Feature\WorkspaceRebase\CommandThatFailedDuringRebase; -use Neos\ContentRepository\Core\NodeType\NodeTypeManager; -use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface; -use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindClosestNodeFilter; -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\Exception\NodeAggregateCurrentlyDoesNotExist; -use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist; -use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; -use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName; use Neos\Flow\Annotations as Flow; -use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; -use Neos\Neos\Domain\Service\NodeTypeNameFactory; /** * @internal @@ -52,8 +22,6 @@ #[Flow\Proxy(false)] final class ConflictsBuilder { - private NodeTypeManager $nodeTypeManager; - /** * @var Conflict[] */ @@ -62,231 +30,20 @@ final class ConflictsBuilder /** * @var array */ - private array $itemsByAffectedNodeAggregateId = []; - - public function __construct( - private ContentRepository $contentRepository, - private NodeLabelGeneratorInterface $nodeLabelGenerator, - private WorkspaceName $workspaceName, - private ?DimensionSpacePoint $preferredDimensionSpacePoint, - ) { - $this->nodeTypeManager = $contentRepository->getNodeTypeManager(); - } + private array $itemsByKey = []; - public function addCommandThatFailedDuringRebase( - CommandThatFailedDuringRebase $commandThatFailedDuringRebase - ): void { - $nodeAggregateId = $this->extractNodeAggregateIdFromCommand( - $commandThatFailedDuringRebase->command - ); - - if ($nodeAggregateId && isset($this->itemsByAffectedNodeAggregateId[$nodeAggregateId->value])) { - return; + public function addConflict(Conflict $conflict): self + { + if (!isset($this->itemsByKey[$conflict->key])) { + $this->itemsByKey[$conflict->key] = $conflict; + $this->items[] = $conflict; } - $conflict = $this->createConflictFromCommandThatFailedDuringRebase( - $commandThatFailedDuringRebase - ); - - $this->items[] = $conflict; - - if ($nodeAggregateId) { - $this->itemsByAffectedNodeAggregateId[$nodeAggregateId->value] = $conflict; - } + return $this; } public function build(): Conflicts { return new Conflicts(...$this->items); } - - private function createConflictFromCommandThatFailedDuringRebase( - CommandThatFailedDuringRebase $commandThatFailedDuringRebase - ): Conflict { - $nodeAggregateId = $this->extractNodeAggregateIdFromCommand( - $commandThatFailedDuringRebase->command - ); - $subgraph = $this->acquireSubgraphFromCommand( - $commandThatFailedDuringRebase->command, - $nodeAggregateId - ); - $affectedSite = $nodeAggregateId - ? $subgraph?->findClosestNode( - $nodeAggregateId, - FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_SITE) - ) - : null; - $affectedDocument = $nodeAggregateId - ? $subgraph?->findClosestNode( - $nodeAggregateId, - FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_DOCUMENT) - ) - : null; - $affectedNode = $nodeAggregateId - ? $subgraph?->findNodeById($nodeAggregateId) - : null; - - return new Conflict( - affectedSite: $affectedSite - ? $this->createIconLabelForNode($affectedSite) - : null, - affectedDocument: $affectedDocument - ? $this->createIconLabelForNode($affectedDocument) - : null, - affectedNode: $affectedNode - ? $this->createIconLabelForNode($affectedNode) - : null, - typeOfChange: $this->createTypeOfChangeFromCommand( - $commandThatFailedDuringRebase->command - ), - reasonForConflict: $this->createReasonForConflictFromException( - $commandThatFailedDuringRebase->exception - ) - ); - } - - private function extractNodeAggregateIdFromCommand(CommandInterface $command): ?NodeAggregateId - { - return match (true) { - $command instanceof MoveNodeAggregate, - $command instanceof SetNodeProperties, - $command instanceof SetSerializedNodeProperties, - $command instanceof CreateNodeAggregateWithNode, - $command instanceof CreateNodeAggregateWithNodeAndSerializedProperties, - $command instanceof TagSubtree, - $command instanceof DisableNodeAggregate, - $command instanceof UntagSubtree, - $command instanceof EnableNodeAggregate, - $command instanceof RemoveNodeAggregate, - $command instanceof ChangeNodeAggregateType, - $command instanceof CreateNodeVariant => - $command->nodeAggregateId, - $command instanceof SetNodeReferences, - $command instanceof SetSerializedNodeReferences => - $command->sourceNodeAggregateId, - default => null - }; - } - - private function acquireSubgraphFromCommand( - CommandInterface $command, - ?NodeAggregateId $nodeAggregateIdForDimensionFallback - ): ?ContentSubgraphInterface { - try { - $contentGraph = $this->contentRepository->getContentGraph($this->workspaceName); - } catch (WorkspaceDoesNotExist) { - return null; - } - - $dimensionSpacePoint = match (true) { - $command instanceof MoveNodeAggregate => - $command->dimensionSpacePoint, - $command instanceof SetNodeProperties, - $command instanceof SetSerializedNodeProperties, - $command instanceof CreateNodeAggregateWithNode, - $command instanceof CreateNodeAggregateWithNodeAndSerializedProperties => - $command->originDimensionSpacePoint->toDimensionSpacePoint(), - $command instanceof SetNodeReferences, - $command instanceof SetSerializedNodeReferences => - $command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint(), - $command instanceof TagSubtree, - $command instanceof DisableNodeAggregate, - $command instanceof UntagSubtree, - $command instanceof EnableNodeAggregate, - $command instanceof RemoveNodeAggregate => - $command->coveredDimensionSpacePoint, - $command instanceof ChangeNodeAggregateType => - null, - $command instanceof CreateNodeVariant => - $command->targetOrigin->toDimensionSpacePoint(), - default => null - }; - - if ($dimensionSpacePoint === null) { - if ($nodeAggregateIdForDimensionFallback === null) { - return null; - } - - $nodeAggregate = $contentGraph - ->findNodeAggregateById( - $nodeAggregateIdForDimensionFallback - ); - - if ($nodeAggregate) { - $dimensionSpacePoint = $this->extractValidDimensionSpacePointFromNodeAggregate( - $nodeAggregate - ); - } - } - - if ($dimensionSpacePoint === null) { - return null; - } - - return $contentGraph->getSubgraph( - $dimensionSpacePoint, - VisibilityConstraints::withoutRestrictions() - ); - } - - private function extractValidDimensionSpacePointFromNodeAggregate( - NodeAggregate $nodeAggregate - ): ?DimensionSpacePoint { - $result = null; - - foreach ($nodeAggregate->coveredDimensionSpacePoints as $coveredDimensionSpacePoint) { - if ($this->preferredDimensionSpacePoint?->equals($coveredDimensionSpacePoint)) { - return $coveredDimensionSpacePoint; - } - $result ??= $coveredDimensionSpacePoint; - } - - return $result; - } - - private function createIconLabelForNode(Node $node): IconLabel - { - $nodeType = $this->nodeTypeManager->getNodeType($node->nodeTypeName); - - return new IconLabel( - icon: $nodeType?->getConfiguration('ui.icon') ?? 'questionmark', - label: $this->nodeLabelGenerator->getLabel($node) - ); - } - - private function createTypeOfChangeFromCommand( - CommandInterface $command - ): ?TypeOfChange { - return match (true) { - $command instanceof CreateNodeAggregateWithNode, - $command instanceof CreateNodeAggregateWithNodeAndSerializedProperties, - $command instanceof CreateNodeVariant => - TypeOfChange::NODE_HAS_BEEN_CREATED, - $command instanceof SetNodeProperties, - $command instanceof SetSerializedNodeProperties, - $command instanceof SetNodeReferences, - $command instanceof SetSerializedNodeReferences, - $command instanceof TagSubtree, - $command instanceof DisableNodeAggregate, - $command instanceof UntagSubtree, - $command instanceof EnableNodeAggregate, - $command instanceof ChangeNodeAggregateType => - TypeOfChange::NODE_HAS_BEEN_CHANGED, - $command instanceof MoveNodeAggregate => - TypeOfChange::NODE_HAS_BEEN_MOVED, - $command instanceof RemoveNodeAggregate => - TypeOfChange::NODE_HAS_BEEN_DELETED, - default => null - }; - } - - private function createReasonForConflictFromException( - \Throwable $exception - ): ?ReasonForConflict { - return match ($exception::class) { - NodeAggregateCurrentlyDoesNotExist::class => - ReasonForConflict::NODE_HAS_BEEN_DELETED, - default => null - }; - } } diff --git a/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php b/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php index 1019caa643..eb6affa906 100644 --- a/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php +++ b/Classes/Application/SyncWorkspace/SyncWorkspaceCommandHandler.php @@ -19,8 +19,8 @@ use Neos\Flow\Annotations as Flow; use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface; use Neos\Neos\Domain\Workspace\WorkspaceProvider; -use Neos\Neos\Ui\Application\Shared\Conflicts; use Neos\Neos\Ui\Application\Shared\ConflictsOccurred; +use Neos\Neos\Ui\Infrastructure\ContentRepository\ConflictsFactory; /** * The application layer level command handler to for rebasing the workspace @@ -52,7 +52,7 @@ public function handle( return new SyncingSucceeded(); } catch (WorkspaceRebaseFailed $e) { - $conflictsBuilder = Conflicts::builder( + $conflictsFactory = new ConflictsFactory( contentRepository: $this->contentRepositoryRegistry ->get($command->contentRepositoryId), nodeLabelGenerator: $this->nodeLabelGenerator, @@ -60,12 +60,8 @@ public function handle( preferredDimensionSpacePoint: $command->preferredDimensionSpacePoint ); - foreach ($e->commandsThatFailedDuringRebase as $commandThatFailedDuringRebase) { - $conflictsBuilder->addCommandThatFailedDuringRebase($commandThatFailedDuringRebase); - } - return new ConflictsOccurred( - conflicts: $conflictsBuilder->build() + conflicts: $conflictsFactory->fromWorkspaceRebaseFailed($e) ); } } diff --git a/Classes/Infrastructure/ContentRepository/ConflictsFactory.php b/Classes/Infrastructure/ContentRepository/ConflictsFactory.php new file mode 100644 index 0000000000..b51d6b0d95 --- /dev/null +++ b/Classes/Infrastructure/ContentRepository/ConflictsFactory.php @@ -0,0 +1,281 @@ +nodeTypeManager = $contentRepository->getNodeTypeManager(); + + $this->workspace = $contentRepository->getWorkspaceFinder() + ->findOneByName($workspaceName); + } + + public function fromWorkspaceRebaseFailed( + WorkspaceRebaseFailed $workspaceRebaseFailed + ): Conflicts { + $conflictsBuilder = Conflicts::builder(); + + foreach ($workspaceRebaseFailed->commandsThatFailedDuringRebase as $commandThatFailedDuringRebase) { + $conflict = $this->createConflictFromCommandThatFailedDuringRebase($commandThatFailedDuringRebase); + $conflictsBuilder->addConflict($conflict); + } + + return $conflictsBuilder->build(); + } + + private function createConflictFromCommandThatFailedDuringRebase( + CommandThatFailedDuringRebase $commandThatFailedDuringRebase + ): Conflict { + $nodeAggregateId = $this->extractNodeAggregateIdFromCommand( + $commandThatFailedDuringRebase->command + ); + $subgraph = $this->acquireSubgraphFromCommand( + $commandThatFailedDuringRebase->command, + $nodeAggregateId + ); + $affectedSite = $nodeAggregateId + ? $subgraph?->findClosestNode( + $nodeAggregateId, + FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_SITE) + ) + : null; + $affectedDocument = $nodeAggregateId + ? $subgraph?->findClosestNode( + $nodeAggregateId, + FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_DOCUMENT) + ) + : null; + $affectedNode = $nodeAggregateId + ? $subgraph?->findNodeById($nodeAggregateId) + : null; + + return new Conflict( + key: $affectedNode + ? $affectedNode->nodeAggregateId->value + : 'command-' . $commandThatFailedDuringRebase->sequenceNumber, + affectedSite: $affectedSite + ? $this->createIconLabelForNode($affectedSite) + : null, + affectedDocument: $affectedDocument + ? $this->createIconLabelForNode($affectedDocument) + : null, + affectedNode: $affectedNode + ? $this->createIconLabelForNode($affectedNode) + : null, + typeOfChange: $this->createTypeOfChangeFromCommand( + $commandThatFailedDuringRebase->command + ), + reasonForConflict: $this->createReasonForConflictFromException( + $commandThatFailedDuringRebase->exception + ) + ); + } + + private function extractNodeAggregateIdFromCommand(CommandInterface $command): ?NodeAggregateId + { + return match (true) { + $command instanceof MoveNodeAggregate, + $command instanceof SetNodeProperties, + $command instanceof SetSerializedNodeProperties, + $command instanceof CreateNodeAggregateWithNode, + $command instanceof CreateNodeAggregateWithNodeAndSerializedProperties, + $command instanceof TagSubtree, + $command instanceof DisableNodeAggregate, + $command instanceof UntagSubtree, + $command instanceof EnableNodeAggregate, + $command instanceof RemoveNodeAggregate, + $command instanceof ChangeNodeAggregateType, + $command instanceof CreateNodeVariant => + $command->nodeAggregateId, + $command instanceof SetNodeReferences, + $command instanceof SetSerializedNodeReferences => + $command->sourceNodeAggregateId, + default => null + }; + } + + private function acquireSubgraphFromCommand( + CommandInterface $command, + ?NodeAggregateId $nodeAggregateIdForDimensionFallback + ): ?ContentSubgraphInterface { + if ($this->workspace === null) { + return null; + } + + $dimensionSpacePoint = match (true) { + $command instanceof MoveNodeAggregate => + $command->dimensionSpacePoint, + $command instanceof SetNodeProperties, + $command instanceof SetSerializedNodeProperties, + $command instanceof CreateNodeAggregateWithNode, + $command instanceof CreateNodeAggregateWithNodeAndSerializedProperties => + $command->originDimensionSpacePoint->toDimensionSpacePoint(), + $command instanceof SetNodeReferences, + $command instanceof SetSerializedNodeReferences => + $command->sourceOriginDimensionSpacePoint->toDimensionSpacePoint(), + $command instanceof TagSubtree, + $command instanceof DisableNodeAggregate, + $command instanceof UntagSubtree, + $command instanceof EnableNodeAggregate, + $command instanceof RemoveNodeAggregate => + $command->coveredDimensionSpacePoint, + $command instanceof ChangeNodeAggregateType => + null, + $command instanceof CreateNodeVariant => + $command->targetOrigin->toDimensionSpacePoint(), + default => null + }; + + if ($dimensionSpacePoint === null) { + if ($nodeAggregateIdForDimensionFallback === null) { + return null; + } + + $nodeAggregate = $this->contentRepository + ->getContentGraph($this->workspace->workspaceName) + ->findNodeAggregateById($nodeAggregateIdForDimensionFallback); + + if ($nodeAggregate) { + $dimensionSpacePoint = $this->extractValidDimensionSpacePointFromNodeAggregate( + $nodeAggregate + ); + } + } + + if ($dimensionSpacePoint === null) { + return null; + } + + return $this->contentRepository + ->getContentGraph($this->workspace->workspaceName) + ->getSubgraph( + $dimensionSpacePoint, + VisibilityConstraints::withoutRestrictions() + ); + } + + private function extractValidDimensionSpacePointFromNodeAggregate( + NodeAggregate $nodeAggregate + ): ?DimensionSpacePoint { + $result = null; + + foreach ($nodeAggregate->coveredDimensionSpacePoints as $coveredDimensionSpacePoint) { + if ($this->preferredDimensionSpacePoint?->equals($coveredDimensionSpacePoint)) { + return $coveredDimensionSpacePoint; + } + $result ??= $coveredDimensionSpacePoint; + } + + return $result; + } + + private function createIconLabelForNode(Node $node): IconLabel + { + $nodeType = $this->nodeTypeManager->getNodeType($node->nodeTypeName); + + return new IconLabel( + icon: $nodeType?->getConfiguration('ui.icon') ?? 'questionmark', + label: $this->nodeLabelGenerator->getLabel($node), + ); + } + + private function createTypeOfChangeFromCommand( + CommandInterface $command + ): ?TypeOfChange { + return match (true) { + $command instanceof CreateNodeAggregateWithNode, + $command instanceof CreateNodeAggregateWithNodeAndSerializedProperties, + $command instanceof CreateNodeVariant => + TypeOfChange::NODE_HAS_BEEN_CREATED, + $command instanceof SetNodeProperties, + $command instanceof SetSerializedNodeProperties, + $command instanceof SetNodeReferences, + $command instanceof SetSerializedNodeReferences, + $command instanceof TagSubtree, + $command instanceof DisableNodeAggregate, + $command instanceof UntagSubtree, + $command instanceof EnableNodeAggregate, + $command instanceof ChangeNodeAggregateType => + TypeOfChange::NODE_HAS_BEEN_CHANGED, + $command instanceof MoveNodeAggregate => + TypeOfChange::NODE_HAS_BEEN_MOVED, + $command instanceof RemoveNodeAggregate => + TypeOfChange::NODE_HAS_BEEN_DELETED, + default => null + }; + } + + private function createReasonForConflictFromException( + \Throwable $exception + ): ?ReasonForConflict { + return match ($exception::class) { + NodeAggregateCurrentlyDoesNotExist::class => + ReasonForConflict::NODE_HAS_BEEN_DELETED, + default => null + }; + } +} From 67468b3abfd396c68a0f8478bb91707365332914 Mon Sep 17 00:00:00 2001 From: Wilhelm Behncke Date: Tue, 23 Apr 2024 13:15:42 +0200 Subject: [PATCH 006/110] TASK: Use conflict DTO `key` property as key for iteration in ConflictList component --- packages/neos-ui-redux-store/src/CR/Syncing/index.ts | 1 + .../Containers/Modals/SyncWorkspaceDialog/ConflictList.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/neos-ui-redux-store/src/CR/Syncing/index.ts b/packages/neos-ui-redux-store/src/CR/Syncing/index.ts index 2c41a72cd8..f34d3c8e2e 100644 --- a/packages/neos-ui-redux-store/src/CR/Syncing/index.ts +++ b/packages/neos-ui-redux-store/src/CR/Syncing/index.ts @@ -32,6 +32,7 @@ export enum ReasonForConflict { } export type Conflict = { + key: string; affectedNode: null | { icon: string; label: string; diff --git a/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ConflictList.tsx b/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ConflictList.tsx index b6a20b94e8..97a9189aa5 100644 --- a/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ConflictList.tsx +++ b/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ConflictList.tsx @@ -23,9 +23,9 @@ export const ConflictList: React.FC<{ }> = (props) => { return (