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: Add "publish all" dialog for partial publish error #3893

Draft
wants to merge 4 commits into
base: 9.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -15,6 +15,7 @@
namespace Neos\Neos\Ui\Application\PublishChangesInDocument;

use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\PartialWorkspaceRebaseFailed;
use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateCurrentlyDoesNotExist;
use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
Expand Down Expand Up @@ -51,7 +52,8 @@ final class PublishChangesInDocumentCommandHandler
*/
public function handle(
PublishChangesInDocumentCommand $command
): PublishSucceeded|ConflictsOccurred {
): PublishSucceeded|ConflictsOccurred|PartialPublishFailed
{
try {
$publishingResult = $this->workspacePublishingService->publishChangesInDocument(
$command->contentRepositoryId,
Expand All @@ -67,6 +69,32 @@ public function handle(
numberOfAffectedChanges: $publishingResult->numberOfPublishedChanges,
baseWorkspaceName: $workspace?->baseWorkspaceName?->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),
isPartialPublish: false
);
} catch (PartialWorkspaceRebaseFailed $e) {
$conflictsFactory = new ConflictsFactory(
contentRepository: $this->contentRepositoryRegistry
->get($command->contentRepositoryId),
nodeLabelGenerator: $this->nodeLabelGenerator,
workspaceName: $command->workspaceName,
preferredDimensionSpacePoint: $command->preferredDimensionSpacePoint
);

return new ConflictsOccurred(
conflicts: $conflictsFactory->fromPartialWorkspaceRebaseFailed($e),
isPartialPublish: true
);
} catch (NodeAggregateCurrentlyDoesNotExist $e) {
throw new \RuntimeException(
$this->getLabel('NodeNotPublishedMissingParentNode'),
Expand All @@ -79,18 +107,7 @@ public function handle(
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)
);
}
}
}
3 changes: 2 additions & 1 deletion Classes/Application/Shared/ConflictsOccurred.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
final readonly class ConflictsOccurred implements \JsonSerializable
{
public function __construct(
public readonly Conflicts $conflicts
public readonly Conflicts $conflicts,
public readonly bool $isPartialPublish = true
) {
}

Expand Down
18 changes: 18 additions & 0 deletions Classes/Infrastructure/ContentRepository/ConflictsFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasTagged;
use Neos\ContentRepository\Core\Feature\SubtreeTagging\Event\SubtreeWasUntagged;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\ConflictingEvent;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\PartialWorkspaceRebaseFailed;
use Neos\ContentRepository\Core\Feature\WorkspaceRebase\Exception\WorkspaceRebaseFailed;
use Neos\ContentRepository\Core\NodeType\NodeTypeManager;
use Neos\ContentRepository\Core\Projection\ContentGraph\ContentSubgraphInterface;
Expand Down Expand Up @@ -87,6 +88,23 @@ public function fromWorkspaceRebaseFailed(
return new Conflicts(...$conflictsByKey);
}

public function fromPartialWorkspaceRebaseFailed(
PartialWorkspaceRebaseFailed $partialWorkspaceRebaseFailed
): Conflicts {
/** @var array<string,Conflict> */
$conflictsByKey = [];

foreach ($partialWorkspaceRebaseFailed->conflictingEvents as $conflictingEvent) {
$conflict = $this->createConflict($conflictingEvent);
if (!array_key_exists($conflict->key, $conflictsByKey)) {
// deduplicate if the conflict affects the same node
$conflictsByKey[$conflict->key] = $conflict;
}
}

return new Conflicts(...$conflictsByKey);
}

private function createConflict(
ConflictingEvent $conflictingEvent
): Conflict {
Expand Down
9 changes: 9 additions & 0 deletions Resources/Private/Translations/en/PublishingDialog.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,15 @@
<trans-unit id="discard.document.success.acknowledge" xml:space="preserve">
<source>OK</source>
</trans-unit>
<trans-unit id="partialPublishFailed.title" xml:space="preserve">
<source>Could not publish all changes in "{scopeTitle}"</source>
</trans-unit>
<trans-unit id="partialPublishFailed.message" xml:space="preserve">
<source>Some changes in this document are dependant on changes in other documents. Do you want to publish all changes to the workspace "{targetWorkspaceName}"?</source>
</trans-unit>
<trans-unit id="partialPublishFailed.publishAll" xml:space="preserve">
<source>Yes, publish all changes</source>
</trans-unit>
</body>
</file>
</xliff>
20 changes: 20 additions & 0 deletions Resources/Private/Translations/en/SyncWorkspaceDialog.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
<trans-unit id="resolutionStrategy.selection.option.DISCARD_ALL.description" xml:space="preserve">
<source>This will discard all changes in your workspace, including those on other sites.</source>
</trans-unit>
<trans-unit id="resolutionStrategy.selection.option.PUBLISH_ALL.label" xml:space="preserve">
<source>Publish all changes to workspace "{workspaceName}"</source>
</trans-unit>
<trans-unit id="resolutionStrategy.selection.option.PUBLISH_ALL.description" xml:space="preserve">
<source>This will publish all changes in your workspace, including those on other sites.</source>
</trans-unit>
<trans-unit id="resolutionStrategy.selection.cancel" xml:space="preserve">
<source>Cancel Synchronization</source>
</trans-unit>
Expand All @@ -58,12 +64,26 @@

Do you wish to proceed? Be careful: This cannot be undone!</source>
</trans-unit>
<trans-unit id="resolutionStrategy.PUBLISH_ALL.confirmation.title" xml:space="preserve">
<source>Publish all changes in workspace "{workspaceName}"</source>
</trans-unit>
<trans-unit id="resolutionStrategy.PUBLISH_ALL.confirmation.message" xml:space="preserve">
<source>You are about to publish all changes in workspace "{workspaceName}". This includes all changes on other sites.

Do you wish to proceed? Be careful: This cannot be undone!</source>
</trans-unit>
<trans-unit id="resolutionStrategy.DISCARD_ALL.confirmation.cancel" xml:space="preserve">
<source>No, cancel</source>
</trans-unit>
<trans-unit id="resolutionStrategy.DISCARD_ALL.confirmation.confirm" xml:space="preserve">
<source>Yes, discard everything</source>
</trans-unit>
<trans-unit id="resolutionStrategy.PUBLISH_ALL.confirmation.cancel" xml:space="preserve">
<source>No, cancel</source>
</trans-unit>
<trans-unit id="resolutionStrategy.PUBLISH_ALL.confirmation.confirm" xml:space="preserve">
<source>Yes, publish everything</source>
</trans-unit>
<trans-unit id="resolutionStrategy.FORCE.confirmation.conflicts.label" xml:space="preserve">
<source>You are about to drop the following changes:</source>
</trans-unit>
Expand Down
9 changes: 5 additions & 4 deletions packages/neos-ui-redux-store/src/CR/Syncing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export enum SyncingPhase {

export enum ResolutionStrategy {
FORCE,
DISCARD_ALL
DISCARD_ALL,
PUBLISH_ALL
}

export enum ReasonForConflict {
Expand Down Expand Up @@ -106,8 +107,8 @@ const confirm = () => createAction(actionTypes.CONFIRMED);
/**
* Signal that conflicts occurred during the ongoing syncing (rebasing) workflow
*/
const resolve = (conflicts: Conflict[]) =>
createAction(actionTypes.CONFLICTS_DETECTED, {conflicts});
const resolve = (conflicts: Conflict[], strategy: ResolutionStrategy) =>
createAction(actionTypes.CONFLICTS_DETECTED, {conflicts, strategy});

/**
* Initiates the process of resolving a conflict that occurred
Expand Down Expand Up @@ -192,7 +193,7 @@ export const reducer = (state: State = defaultState, action: Action): State => {
process: {
phase: SyncingPhase.CONFLICT,
conflicts: action.payload.conflicts,
strategy: null
strategy: action.payload.strategy
}
};
}
Expand Down
6 changes: 3 additions & 3 deletions packages/neos-ui-sagas/src/Publish/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type PublishingResponse =
numberOfAffectedChanges: number;
}
}
| { conflicts: Conflict[] }
| { conflicts: Conflict[], isPartialPublish: boolean }
| { error: AnyError };

export function * watchPublishing({routes}: {routes: Routes}) {
Expand Down Expand Up @@ -75,6 +75,7 @@ export function * watchPublishing({routes}: {routes: Routes}) {

yield takeEvery(actionTypes.CR.Publishing.STARTED, function * publishingWorkflow(action: ReturnType<typeof actions.CR.Publishing.start>) {
const confirmed = yield * waitForConfirmation();

if (!confirmed) {
return;
}
Expand All @@ -92,7 +93,6 @@ export function * watchPublishing({routes}: {routes: Routes}) {
const ancestorId: NodeContextPath = ancestorIdSelector
? yield select(ancestorIdSelector)
: null;

function * attemptToPublishOrDiscard(): Generator<any, any, any> {
const result: PublishingResponse = scope === PublishingScope.ALL
? yield call(endpoint as any, workspaceName)
Expand All @@ -104,7 +104,7 @@ export function * watchPublishing({routes}: {routes: Routes}) {
} else if ('conflicts' in result) {
yield put(actions.CR.Publishing.conflicts());
const conflictsWereResolved: boolean =
yield * resolveConflicts(result.conflicts);
yield * resolveConflicts(result.conflicts, result.isPartialPublish);

if (conflictsWereResolved) {
yield put(actions.CR.Publishing.resolveConflicts());
Expand Down
61 changes: 51 additions & 10 deletions packages/neos-ui-sagas/src/Sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const handleWindowBeforeUnload = (event: BeforeUnloadEvent) => {

type SyncWorkspaceResult =
| { success: true }
| { conflicts: Conflict[] }
| { conflicts: Conflict[], isPartialPublish: false }
| { error: AnyError };

export function * watchSyncing({routes}: {routes: Routes}) {
Expand Down Expand Up @@ -75,7 +75,7 @@ export const makeSyncPersonalWorkspace = (deps: {
yield * refreshAfterSyncing();
yield put(actions.CR.Syncing.succeed());
} else if ('conflicts' in result) {
yield * resolveConflicts(result.conflicts);
yield * resolveConflicts(result.conflicts, result.isPartialPublish);
} else {
yield put(actions.CR.Syncing.fail(result.error));
}
Expand All @@ -93,10 +93,12 @@ export const makeResolveConflicts = (deps: {
syncPersonalWorkspace: ReturnType<typeof makeSyncPersonalWorkspace>
}) => {
const discardAll = makeDiscardAll(deps);
const publishAll = makePublishAll();

function * resolveConflicts(conflicts: Conflict[]): any {
function * resolveConflicts(conflicts: Conflict[], isPartialPublish: boolean): any {
while (true) {
yield put(actions.CR.Syncing.resolve(conflicts));
const defaultResolutionStrategy = isPartialPublish ? ResolutionStrategy.PUBLISH_ALL : ResolutionStrategy.FORCE
yield put(actions.CR.Syncing.resolve(conflicts, defaultResolutionStrategy));

const {started}: {
cancelled: null | ReturnType<typeof actions.CR.Syncing.cancel>;
Expand All @@ -105,10 +107,8 @@ export const makeResolveConflicts = (deps: {
cancelled: take(actionTypes.CR.Syncing.CANCELLED),
started: take(actionTypes.CR.Syncing.RESOLUTION_STARTED)
});

if (started) {
const {payload: {strategy}} = started;

if (strategy === ResolutionStrategy.FORCE) {
if (yield * waitForResolutionConfirmation()) {
yield * deps.syncPersonalWorkspace(true);
Expand All @@ -119,8 +119,21 @@ export const makeResolveConflicts = (deps: {
}

if (strategy === ResolutionStrategy.DISCARD_ALL) {
yield * discardAll();
return true;
if (yield * waitForResolutionConfirmation()) {
yield * discardAll();
return true;
}

continue;
}

if (strategy === ResolutionStrategy.PUBLISH_ALL) {
if (yield * waitForResolutionConfirmation()) {
yield * publishAll();
return true;
}

continue;
}
}

Expand Down Expand Up @@ -163,7 +176,7 @@ const makeDiscardAll = (deps: {
PublishingMode.DISCARD,
PublishingScope.ALL
));

yield put(actions.CR.Publishing.confirm());
const {cancelled, failed}: {
cancelled: null | ReturnType<typeof actions.CR.Publishing.cancel>;
failed: null | ReturnType<typeof actions.CR.Publishing.fail>;
Expand All @@ -179,14 +192,42 @@ const makeDiscardAll = (deps: {
} else if (failed) {
yield put(actions.CR.Syncing.finish());
} else {
yield put(actions.CR.Syncing.confirmResolution());
yield put(actions.CR.Syncing.finish());
yield * deps.syncPersonalWorkspace(false);
}
}

return discardAll;
}

const makePublishAll = () => {
function * publishAll() {
yield put(actions.CR.Publishing.start(
PublishingMode.PUBLISH,
PublishingScope.SITE
));
yield put(actions.CR.Publishing.confirm());
const {cancelled, failed, finished}: {
cancelled: null | ReturnType<typeof actions.CR.Publishing.cancel>;
failed: null | ReturnType<typeof actions.CR.Publishing.fail>;
finished: null | ReturnType<typeof actions.CR.Publishing.finish>;
} = yield race({
cancelled: take(actionTypes.CR.Publishing.CANCELLED),
failed: take(actionTypes.CR.Publishing.FAILED),
finished: take(actionTypes.CR.Publishing.FINISHED)
});
if (cancelled) {
yield put(actions.CR.Syncing.cancelResolution());
} else if (failed) {
yield put(actions.CR.Syncing.finish());
} else {
yield put(actions.CR.Syncing.finish());
}
}

return publishAll;
}

const makeRefreshAfterSyncing = (deps: {
routes: Routes
}) => {
Expand Down
Loading
Loading