Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: Integrate conflict resolution with publish/discard dialog workflow #3769

Merged
merged 24 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
aaeb68a
TASK: Move `Conflicts` concept to `Application\Shared` namespace
grebaldi Apr 22, 2024
5ff99a8
TASK: Add command handlers for all Publish-related commands
grebaldi Apr 22, 2024
10c0098
TASK: Ensure that all Publish-related command handlers throw `Conflic…
grebaldi Apr 22, 2024
852bad1
TASK: Turn `ConflictsOccurred` into a Result DTO rather than an excep…
grebaldi Apr 22, 2024
aec75b0
TASK: Move logic to create Conflicts from WorkspaceRebaseFailed into …
grebaldi Apr 23, 2024
67468b3
TASK: Use conflict DTO `key` property as key for iteration in Conflic…
grebaldi Apr 23, 2024
24927dc
TASK: Trigger conflict resolution from within `Publish` saga
grebaldi Apr 23, 2024
f23176b
BUGFIX: Remove race condition in `watchNodeInformationChanges`
grebaldi May 20, 2024
4205f09
BUGFIX: Wait and reload before syncing in E2E test
grebaldi May 21, 2024
98d002a
TASK: Stabilize E2E tests for synving feature
grebaldi May 27, 2024
d8c93a0
BUGFIX: Fix undefined state when cancelling sync operations
grebaldi May 28, 2024
f2526fd
TASK: Handle edge case when automatic syncing during "publish documen…
grebaldi May 28, 2024
e0bcbd1
Merge remote-tracking branch 'origin/9.0' into feature/conflict-resol…
mhsdesign Sep 30, 2024
c7c160f
TASK: Fix php code
mhsdesign Sep 30, 2024
b072764
Merge remote-tracking branch 'origin/9.0' into feature/conflict-resol…
mhsdesign Oct 17, 2024
64ac4bc
TASK: Adjust to changes in 9.0 (workspace metadata)
mhsdesign Oct 17, 2024
9c70968
TASK: Dare to unskip tests again:)
mhsdesign Oct 17, 2024
d3090d4
Merge remote-tracking branch 'origin/9.0' into feature/conflict-resol…
mhsdesign Oct 26, 2024
6778a5a
TASK: Make sure e2e test fail if the Publish Result Dialog shows an e…
mhsdesign Oct 27, 2024
d2baeb7
TASK: Don't absorb unexpected exception if publishing failed
mhsdesign Oct 27, 2024
7468ffb
TASK: Adjust to changes of Publishing V3
mhsdesign Oct 27, 2024
15c13ab
TASK: Avoid use of internal `sequenceNumber` for building commands th…
mhsdesign Oct 28, 2024
36a5de7
TASK: Inline `ConflictsBuilder` to better document deduplication logic
mhsdesign Oct 28, 2024
68fdde5
Merge remote-tracking branch 'origin/9.0' into feature/conflict-resol…
mhsdesign Oct 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,43 @@

declare(strict_types=1);

namespace Neos\Neos\Ui\Application;
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;
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,
public WorkspaceName $workspaceName,
public NodeAggregateId $documentId,
public ?DimensionSpacePoint $preferredDimensionSpacePoint,
) {
}

/**
* @param array<string,string> $values
* @param array{contentRepositoryId:string,workspaceName:string,documentId:string,preferredDimensionSpacePoint?:array<string,string[]>} $values
*/
public static function fromArray(array $values): self
{
return new self(
ContentRepositoryId::fromString($values['contentRepositoryId']),
WorkspaceName::fromString($values['workspaceName']),
NodeAggregateId::fromString($values['documentId']),
isset($values['preferredDimensionSpacePoint']) && !empty($values['preferredDimensionSpacePoint'])
? DimensionSpacePoint::fromLegacyDimensionArray($values['preferredDimensionSpacePoint'])
: null,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

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

declare(strict_types=1);

namespace Neos\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\NodeLabel\NodeLabelGeneratorInterface;
use Neos\Neos\Domain\Workspace\WorkspaceProvider;
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
* all changes recorded for a given document
*
* @internal for communication within the Neos UI only
*/
#[Flow\Scope("singleton")]
final class PublishChangesInDocumentCommandHandler
{
use TranslationTrait;

#[Flow\Inject]
protected ContentRepositoryRegistry $contentRepositoryRegistry;

#[Flow\Inject]
protected WorkspaceProvider $workspaceProvider;

#[Flow\Inject]
protected NodeLabelGeneratorInterface $nodeLabelGenerator;

/**
* @throws NodeAggregateCurrentlyDoesNotExist
*/
public function handle(
PublishChangesInDocumentCommand $command
): PublishSucceeded|ConflictsOccurred {
try {
$workspace = $this->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
);
} catch (WorkspaceRebaseFailed $e) {
$conflictsFactory = new ConflictsFactory(
contentRepository: $this->contentRepositoryRegistry
->get($command->contentRepositoryId),
nodeLabelGenerator: $this->nodeLabelGenerator,
workspaceName: $command->workspaceName,
preferredDimensionSpacePoint: $command->preferredDimensionSpacePoint
);

return new ConflictsOccurred(
conflicts: $conflictsFactory->fromWorkspaceRebaseFailed($e)
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,43 @@

declare(strict_types=1);

namespace Neos\Neos\Ui\Application;
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;
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,
public WorkspaceName $workspaceName,
public NodeAggregateId $siteId,
public ?DimensionSpacePoint $preferredDimensionSpacePoint,
) {
}

/**
* @param array<string,string> $values
* @param array{contentRepositoryId:string,workspaceName:string,siteId:string,preferredDimensionSpacePoint?:array<string,string[]>} $values
*/
public static function fromArray(array $values): self
{
return new self(
ContentRepositoryId::fromString($values['contentRepositoryId']),
WorkspaceName::fromString($values['workspaceName']),
NodeAggregateId::fromString($values['siteId']),
isset($values['preferredDimensionSpacePoint']) && !empty($values['preferredDimensionSpacePoint'])
? DimensionSpacePoint::fromLegacyDimensionArray($values['preferredDimensionSpacePoint'])
: null,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

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

declare(strict_types=1);

namespace Neos\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\NodeLabel\NodeLabelGeneratorInterface;
use Neos\Neos\Domain\Workspace\WorkspaceProvider;
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
* all changes recorded for a given site
*
* @internal for communication within the Neos UI only
*/
#[Flow\Scope("singleton")]
final class PublishChangesInSiteCommandHandler
{
#[Flow\Inject]
protected ContentRepositoryRegistry $contentRepositoryRegistry;

#[Flow\Inject]
protected WorkspaceProvider $workspaceProvider;

#[Flow\Inject]
protected NodeLabelGeneratorInterface $nodeLabelGenerator;

public function handle(
PublishChangesInSiteCommand $command
): PublishSucceeded|ConflictsOccurred {
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) {
$conflictsFactory = new ConflictsFactory(
contentRepository: $this->contentRepositoryRegistry
->get($command->contentRepositoryId),
nodeLabelGenerator: $this->nodeLabelGenerator,
workspaceName: $command->workspaceName,
preferredDimensionSpacePoint: $command->preferredDimensionSpacePoint
);

return new ConflictsOccurred(
conflicts: $conflictsFactory->fromWorkspaceRebaseFailed($e)
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@

declare(strict_types=1);

namespace Neos\Neos\Ui\Application\SyncWorkspace;
namespace Neos\Neos\Ui\Application\Shared;

use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
use Neos\Flow\Annotations as Flow;

/**
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,9 @@

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;
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
Expand All @@ -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
Expand Down
49 changes: 49 additions & 0 deletions Classes/Application/Shared/ConflictsBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

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

declare(strict_types=1);

namespace Neos\Neos\Ui\Application\Shared;

use Neos\Flow\Annotations as Flow;

/**
* @internal
*/
#[Flow\Proxy(false)]
final class ConflictsBuilder
{
/**
* @var Conflict[]
*/
private array $items = [];

/**
* @var array<string,Conflict>
*/
private array $itemsByKey = [];

public function addConflict(Conflict $conflict): self
{
if (!isset($this->itemsByKey[$conflict->key])) {
$this->itemsByKey[$conflict->key] = $conflict;
$this->items[] = $conflict;
}

return $this;
}

public function build(): Conflicts
{
return new Conflicts(...$this->items);
}
}
Loading
Loading