From 7a22dc25b1cfc9033867a65333b566980384881c Mon Sep 17 00:00:00 2001 From: pKallert Date: Mon, 16 Dec 2024 13:28:08 +0100 Subject: [PATCH] Feature: Use existing conflict dialogue --- ...PublishChangesInDocumentCommandHandler.php | 18 ++- .../Application/Shared/ConflictsOccurred.php | 3 +- .../Shared/PartialPublishFailed.php | 36 ------ .../ContentRepository/ConflictsFactory.php | 18 +++ .../Translations/en/SyncWorkspaceDialog.xlf | 20 +++ .../src/CR/Publishing/index.ts | 17 --- .../src/CR/Syncing/index.ts | 9 +- packages/neos-ui-sagas/src/Publish/index.ts | 6 +- packages/neos-ui-sagas/src/Sync/index.ts | 47 ++++++- .../PublishAllConfirmationDialog.tsx | 116 ------------------ .../PublishingDialog/PublishingDialog.tsx | 23 +--- .../ResolutionStrategyConfirmationDialog.tsx | 72 +++++++++++ .../ResolutionStrategySelectionDialog.tsx | 17 +++ 13 files changed, 193 insertions(+), 209 deletions(-) delete mode 100644 Classes/Application/Shared/PartialPublishFailed.php delete mode 100644 packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishAllConfirmationDialog.tsx diff --git a/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php index 0119da7c5e..78ef6d12cc 100644 --- a/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php +++ b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php @@ -24,7 +24,6 @@ use Neos\Neos\Domain\Service\WorkspacePublishingService; use Neos\Neos\Ui\Application\Shared\ConflictsOccurred; use Neos\Neos\Ui\Application\Shared\PublishSucceeded; -use Neos\Neos\Ui\Application\Shared\PartialPublishFailed; use Neos\Neos\Ui\Controller\TranslationTrait; use Neos\Neos\Ui\Infrastructure\ContentRepository\ConflictsFactory; @@ -80,14 +79,21 @@ public function handle( ); return new ConflictsOccurred( - conflicts: $conflictsFactory->fromWorkspaceRebaseFailed($e) + conflicts: $conflictsFactory->fromWorkspaceRebaseFailed($e), + isPartialPublish: false ); } catch (PartialWorkspaceRebaseFailed $e) { - $workspace = $this->contentRepositoryRegistry->get($command->contentRepositoryId)->findWorkspaceByName( - $command->workspaceName + $conflictsFactory = new ConflictsFactory( + contentRepository: $this->contentRepositoryRegistry + ->get($command->contentRepositoryId), + nodeLabelGenerator: $this->nodeLabelGenerator, + workspaceName: $command->workspaceName, + preferredDimensionSpacePoint: $command->preferredDimensionSpacePoint ); - return new PartialPublishFailed( - baseWorkspaceName: $workspace?->baseWorkspaceName?->value + + return new ConflictsOccurred( + conflicts: $conflictsFactory->fromPartialWorkspaceRebaseFailed($e), + isPartialPublish: true ); } catch (NodeAggregateCurrentlyDoesNotExist $e) { throw new \RuntimeException( diff --git a/Classes/Application/Shared/ConflictsOccurred.php b/Classes/Application/Shared/ConflictsOccurred.php index fcf1817f5a..6c721a4b47 100644 --- a/Classes/Application/Shared/ConflictsOccurred.php +++ b/Classes/Application/Shared/ConflictsOccurred.php @@ -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 ) { } diff --git a/Classes/Application/Shared/PartialPublishFailed.php b/Classes/Application/Shared/PartialPublishFailed.php deleted file mode 100644 index bbfc4438db..0000000000 --- a/Classes/Application/Shared/PartialPublishFailed.php +++ /dev/null @@ -1,36 +0,0 @@ - get_object_vars($this) - ]; - } -} diff --git a/Classes/Infrastructure/ContentRepository/ConflictsFactory.php b/Classes/Infrastructure/ContentRepository/ConflictsFactory.php index 214e7bcd1c..4a5f768454 100644 --- a/Classes/Infrastructure/ContentRepository/ConflictsFactory.php +++ b/Classes/Infrastructure/ContentRepository/ConflictsFactory.php @@ -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; @@ -87,6 +88,23 @@ public function fromWorkspaceRebaseFailed( return new Conflicts(...$conflictsByKey); } + public function fromPartialWorkspaceRebaseFailed( + PartialWorkspaceRebaseFailed $partialWorkspaceRebaseFailed + ): Conflicts { + /** @var array */ + $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 { diff --git a/Resources/Private/Translations/en/SyncWorkspaceDialog.xlf b/Resources/Private/Translations/en/SyncWorkspaceDialog.xlf index e943c7056a..29f78cdc5e 100644 --- a/Resources/Private/Translations/en/SyncWorkspaceDialog.xlf +++ b/Resources/Private/Translations/en/SyncWorkspaceDialog.xlf @@ -44,6 +44,12 @@ This will discard all changes in your workspace, including those on other sites. + + Publish all changes to workspace "{workspaceName}" + + + This will publish all changes in your workspace, including those on other sites. + Cancel Synchronization @@ -58,12 +64,26 @@ Do you wish to proceed? Be careful: This cannot be undone! + + Publish all changes in workspace "{workspaceName}" + + + 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! + No, cancel Yes, discard everything + + No, cancel + + + Yes, publish everything + You are about to drop the following changes: diff --git a/packages/neos-ui-redux-store/src/CR/Publishing/index.ts b/packages/neos-ui-redux-store/src/CR/Publishing/index.ts index 7bc4e1487a..7191709d60 100644 --- a/packages/neos-ui-redux-store/src/CR/Publishing/index.ts +++ b/packages/neos-ui-redux-store/src/CR/Publishing/index.ts @@ -26,7 +26,6 @@ export enum PublishingPhase { START, ONGOING, CONFLICTS, - PARTIALLYFAILED, SUCCESS, ERROR } @@ -38,7 +37,6 @@ export type State = null | { | { phase: PublishingPhase.START } | { phase: PublishingPhase.ONGOING } | { phase: PublishingPhase.CONFLICTS } - | { phase: PublishingPhase.PARTIALLYFAILED } | { phase: PublishingPhase.ERROR; error: null | AnyError; @@ -58,9 +56,7 @@ export enum actionTypes { CONFLICTS_OCCURRED = '@neos/neos-ui/CR/Publishing/CONFLICTS_OCCURRED', CONFLICTS_RESOLVED = '@neos/neos-ui/CR/Publishing/CONFLICTS_RESOLVED', FAILED = '@neos/neos-ui/CR/Publishing/FAILED', - PARTIALLYFAILED = '@neos/neos-ui/CR/Publishing/PARTIALLYFAILED', RETRIED = '@neos/neos-ui/CR/Publishing/RETRIED', - RETRIEDWITHSTATE = '@neos/neos-ui/CR/Publishing/RETRIEDWITHSTATE', SUCEEDED = '@neos/neos-ui/CR/Publishing/SUCEEDED', ACKNOWLEDGED = '@neos/neos-ui/CR/Publishing/ACKNOWLEDGED', FINISHED = '@neos/neos-ui/CR/Publishing/FINISHED' @@ -98,11 +94,6 @@ const resolveConflicts = () => createAction(actionTypes.CONFLICTS_RESOLVED); const fail = (error: null | AnyError) => createAction(actionTypes.FAILED, {error}); -/** - * Signal that the ongoing publish/discard workflow has partially failed - */ -const partialFail = () => createAction(actionTypes.PARTIALLYFAILED); - /** * Attempt to retry a failed publish/discard workflow */ @@ -134,7 +125,6 @@ export const actions = { conflicts, resolveConflicts, fail, - partialFail, retry, succeed, acknowledge, @@ -193,13 +183,6 @@ export const reducer = (state: State = defaultState, action: Action): State => { error: action.payload.error } }; - case actionTypes.PARTIALLYFAILED: - return { - ...state, - process: { - phase: PublishingPhase.PARTIALLYFAILED - } - }; case actionTypes.RETRIED: return { ...state, 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 ea6b159677..c6f1c7b853 100644 --- a/packages/neos-ui-redux-store/src/CR/Syncing/index.ts +++ b/packages/neos-ui-redux-store/src/CR/Syncing/index.ts @@ -24,7 +24,8 @@ export enum SyncingPhase { export enum ResolutionStrategy { FORCE, - DISCARD_ALL + DISCARD_ALL, + PUBLISH_ALL } export enum ReasonForConflict { @@ -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 @@ -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 } }; } diff --git a/packages/neos-ui-sagas/src/Publish/index.ts b/packages/neos-ui-sagas/src/Publish/index.ts index 34393342f2..91c57b5112 100644 --- a/packages/neos-ui-sagas/src/Publish/index.ts +++ b/packages/neos-ui-sagas/src/Publish/index.ts @@ -36,10 +36,10 @@ type PublishingResponse = } | { partialPublishFail: { - numberOfAffectedChanges: number; + conflicts: Conflict[]; } } - | { conflicts: Conflict[] } + | { conflicts: Conflict[], isPartialPublish: boolean } | { error: AnyError }; export function * watchPublishing({routes}: {routes: Routes}) { @@ -109,7 +109,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()); diff --git a/packages/neos-ui-sagas/src/Sync/index.ts b/packages/neos-ui-sagas/src/Sync/index.ts index a6f2fca4ab..fccac1f665 100644 --- a/packages/neos-ui-sagas/src/Sync/index.ts +++ b/packages/neos-ui-sagas/src/Sync/index.ts @@ -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}) { @@ -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)); } @@ -93,10 +93,12 @@ export const makeResolveConflicts = (deps: { syncPersonalWorkspace: ReturnType }) => { const discardAll = makeDiscardAll(deps); + const publishAll = makePublishAll(deps); - 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; @@ -122,6 +124,11 @@ export const makeResolveConflicts = (deps: { yield * discardAll(); return true; } + + if (strategy === ResolutionStrategy.PUBLISH_ALL) { + yield * publishAll(); + return true; + } } return false; @@ -187,6 +194,38 @@ const makeDiscardAll = (deps: { return discardAll; } +const makePublishAll = (deps: { + syncPersonalWorkspace: ReturnType; +}) => { + function * publishAll() { + yield put(actions.CR.Publishing.start( + PublishingMode.PUBLISH, + PublishingScope.ALL + )); + + const {cancelled, failed}: { + cancelled: null | ReturnType; + failed: null | ReturnType; + finished: null | ReturnType; + } = 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.confirmResolution()); + yield * deps.syncPersonalWorkspace(false); + } + } + + return publishAll; +} + const makeRefreshAfterSyncing = (deps: { routes: Routes }) => { diff --git a/packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishAllConfirmationDialog.tsx b/packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishAllConfirmationDialog.tsx deleted file mode 100644 index c5714dc40f..0000000000 --- a/packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishAllConfirmationDialog.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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. - */ -import React from 'react'; - -import {Button, Dialog, Icon} from '@neos-project/react-ui-components'; -import I18n from '@neos-project/neos-ui-i18n'; -import {PublishingMode, PublishingPhase, PublishingScope} from '@neos-project/neos-ui-redux-store/src/CR/Publishing'; - -import {Diagram} from './Diagram'; - -import style from './style.module.css'; - -type ConfirmationDialogProps = { - mode: PublishingMode; - scope: PublishingScope; - scopeTitle: string; - sourceWorkspaceName: string; - targetWorkspaceName: null | string; - numberOfChanges: number; - onAbort: () => void; - onConfirm: () => void; -} - -export const PublishAllConfirmationDialog: React.FC = (props) => { - const variant = { - id: '', - style: 'error', - icon: { - title: 'exclamation-triangle', - confirm: 'exclamation-triangle' - }, - label: { - title: { - id: 'Neos.Neos.Ui:PublishingDialog:partialPublishFailed.title', - fallback: (props: { scopeTitle: string; }) => - `Could not publish all changes in "${props.scopeTitle}"` - }, - message: { - id: 'Neos.Neos.Ui:PublishingDialog:partialPublishFailed.message', - fallback: (props: { scopeTitle: string; targetWorkspaceName: null | string; }) => - `Some changes in this document are dependent on changes in other documents. Do you want to publish all changes to the workspace "${props.targetWorkspaceName}"?` - }, - cancel: { - id: 'Neos.Neos.Ui:PublishingDialog:publish.all.confirmation.cancel', - fallback: 'No, cancel' - }, - confirm: { - id: 'Neos.Neos.Ui:PublishingDialog:partialPublishFailed.publishAll', - fallback: 'Yes, publish all changes' - } - } - } - - return ( - - - , - - ]} - title={
- - - - -
} - onRequestClose={props.onAbort} - type={variant.style} - isOpen - autoFocus - theme={undefined as any} - style={undefined as any} - > -
- - -
-
- ); -}; diff --git a/packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishingDialog.tsx b/packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishingDialog.tsx index 25dfa8036d..7997c5916f 100644 --- a/packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishingDialog.tsx +++ b/packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishingDialog.tsx @@ -23,7 +23,6 @@ import { import {ConfirmationDialog} from './ConfirmationDialog'; import {ProcessIndicator} from './ProcessIndicator'; import {ResultDialog} from './ResultDialog'; -import {PublishAllConfirmationDialog} from './PublishAllConfirmationDialog'; const { publishableNodesSelector, @@ -47,7 +46,6 @@ type PublishingDialogHandlers = { confirm: () => void; retry: () => void; acknowledge: () => void; - start: (mode: PublishingMode, scope: PublishingScope) => void; } type PublishingDialogProps = @@ -67,11 +65,6 @@ const PublishingDialog: React.FC = (props) => { props.acknowledge(); }, []); - const handlePublishAllClick = React.useCallback(() => { - props.start(PublishingMode.PUBLISH, PublishingScope.SITE); - props.confirm(); - }, []); - if (props.publishingState === null) { return null; } @@ -121,19 +114,6 @@ const PublishingDialog: React.FC = (props) => { onAcknowledge={handleAcknowledge} /> ); - case PublishingPhase.PARTIALLYFAILED: - return ( - - ); } }; @@ -180,6 +160,5 @@ export default connect((state: GlobalState): PublishingDialogProperties => { confirm: (actions as any).CR.Publishing.confirm, cancel: (actions as any).CR.Publishing.cancel, retry: (actions as any).CR.Publishing.retry, - acknowledge: (actions as any).CR.Publishing.acknowledge, - start: (actions as any).CR.Publishing.start + acknowledge: (actions as any).CR.Publishing.acknowledge })(PublishingDialog); diff --git a/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ResolutionStrategyConfirmationDialog.tsx b/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ResolutionStrategyConfirmationDialog.tsx index c0e67a104a..139f4d76ad 100644 --- a/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ResolutionStrategyConfirmationDialog.tsx +++ b/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ResolutionStrategyConfirmationDialog.tsx @@ -34,6 +34,9 @@ export const ResolutionStrategyConfirmationDialog: React.FC<{ switch (props.strategy) { case ResolutionStrategy.FORCE: return (); + + case ResolutionStrategy.PUBLISH_ALL: + return (); case ResolutionStrategy.DISCARD_ALL: default: return (); @@ -182,3 +185,72 @@ const DiscardAllConfirmationDialog: React.FC<{ ); } +const PublishAllConfirmationDialog: React.FC<{ + workspaceName: WorkspaceName; + totalNumberOfChangesInWorkspace: number; + onCancelConflictResolution: () => void; + onConfirmResolutionStrategy: () => void; +}> = (props) => { + return ( + + + , + + ]} + title={ +
+ + +
+ } + onRequestClose={props.onCancelConflictResolution} + type="error" + isOpen + autoFocus + theme={undefined as any} + style={undefined as any} + > +
+ + +
+
+ ); +} diff --git a/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ResolutionStrategySelectionDialog.tsx b/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ResolutionStrategySelectionDialog.tsx index c4feb6f686..f086027b1a 100644 --- a/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ResolutionStrategySelectionDialog.tsx +++ b/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ResolutionStrategySelectionDialog.tsx @@ -47,6 +47,20 @@ const VARIANTS_BY_RESOLUTION_STRATEGY = { fallback: 'This will discard all changes in your workspace, including those on other sites.' } } + }, + [ResolutionStrategy.PUBLISH_ALL]: { + icon: 'check-double', + labels: { + label: { + id: 'Neos.Neos.Ui:SyncWorkspaceDialog:resolutionStrategy.selection.option.PUBLISH_ALL.label', + fallback: (props: {workspaceName: WorkspaceName}) => + `Publish all changes in workspace "${props.workspaceName}"` + }, + description: { + id: 'Neos.Neos.Ui:SyncWorkspaceDialog:resolutionStrategy.selection.option.PUBLISH_ALL.description', + fallback: 'This will publish all changes in your workspace, including those on other sites.' + } + } } } as const; @@ -56,6 +70,9 @@ const OPTIONS_FOR_RESOLUTION_STRATEGY_SELECTION = [ }, { value: ResolutionStrategy.DISCARD_ALL + }, + { + value: ResolutionStrategy.PUBLISH_ALL } ] as const;