From c1fe4f53c2b6fc361d4d162dc4d5a4a73db38edd Mon Sep 17 00:00:00 2001 From: pKallert Date: Tue, 10 Dec 2024 11:57:03 +0100 Subject: [PATCH 1/4] Feature: Add basic popup for partial publish --- ...PublishChangesInDocumentCommandHandler.php | 35 ++++-- .../Shared/PartialPublishFailed.php | 36 ++++++ .../Translations/en/PublishingDialog.xlf | 9 ++ .../src/CR/Publishing/index.ts | 17 +++ packages/neos-ui-sagas/src/Publish/index.ts | 7 ++ .../PublishAllConfirmationDialog.tsx | 116 ++++++++++++++++++ .../PublishingDialog/PublishingDialog.tsx | 23 +++- 7 files changed, 230 insertions(+), 13 deletions(-) create mode 100644 Classes/Application/Shared/PartialPublishFailed.php create 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 d090fdb815..0119da7c5e 100644 --- a/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php +++ b/Classes/Application/PublishChangesInDocument/PublishChangesInDocumentCommandHandler.php @@ -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; @@ -23,6 +24,7 @@ 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; @@ -51,7 +53,8 @@ final class PublishChangesInDocumentCommandHandler */ public function handle( PublishChangesInDocumentCommand $command - ): PublishSucceeded|ConflictsOccurred { + ): PublishSucceeded|ConflictsOccurred|PartialPublishFailed + { try { $publishingResult = $this->workspacePublishingService->publishChangesInDocument( $command->contentRepositoryId, @@ -67,6 +70,25 @@ 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) + ); + } catch (PartialWorkspaceRebaseFailed $e) { + $workspace = $this->contentRepositoryRegistry->get($command->contentRepositoryId)->findWorkspaceByName( + $command->workspaceName + ); + return new PartialPublishFailed( + baseWorkspaceName: $workspace?->baseWorkspaceName?->value + ); } catch (NodeAggregateCurrentlyDoesNotExist $e) { throw new \RuntimeException( $this->getLabel('NodeNotPublishedMissingParentNode'), @@ -79,18 +101,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) - ); } } } diff --git a/Classes/Application/Shared/PartialPublishFailed.php b/Classes/Application/Shared/PartialPublishFailed.php new file mode 100644 index 0000000000..bbfc4438db --- /dev/null +++ b/Classes/Application/Shared/PartialPublishFailed.php @@ -0,0 +1,36 @@ + get_object_vars($this) + ]; + } +} diff --git a/Resources/Private/Translations/en/PublishingDialog.xlf b/Resources/Private/Translations/en/PublishingDialog.xlf index 3c86b9ce2b..7ca6fab54b 100644 --- a/Resources/Private/Translations/en/PublishingDialog.xlf +++ b/Resources/Private/Translations/en/PublishingDialog.xlf @@ -218,6 +218,15 @@ OK + + Could not publish all changes in "{scopeTitle}" + + + Some changes in this document are dependant on changes in other documents. Do you want to publish all changes to the workspace "{targetWorkspaceName}"? + + + Yes, publish all 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 7191709d60..7bc4e1487a 100644 --- a/packages/neos-ui-redux-store/src/CR/Publishing/index.ts +++ b/packages/neos-ui-redux-store/src/CR/Publishing/index.ts @@ -26,6 +26,7 @@ export enum PublishingPhase { START, ONGOING, CONFLICTS, + PARTIALLYFAILED, SUCCESS, ERROR } @@ -37,6 +38,7 @@ export type State = null | { | { phase: PublishingPhase.START } | { phase: PublishingPhase.ONGOING } | { phase: PublishingPhase.CONFLICTS } + | { phase: PublishingPhase.PARTIALLYFAILED } | { phase: PublishingPhase.ERROR; error: null | AnyError; @@ -56,7 +58,9 @@ 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' @@ -94,6 +98,11 @@ 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 */ @@ -125,6 +134,7 @@ export const actions = { conflicts, resolveConflicts, fail, + partialFail, retry, succeed, acknowledge, @@ -183,6 +193,13 @@ 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-sagas/src/Publish/index.ts b/packages/neos-ui-sagas/src/Publish/index.ts index df2c098ee4..34393342f2 100644 --- a/packages/neos-ui-sagas/src/Publish/index.ts +++ b/packages/neos-ui-sagas/src/Publish/index.ts @@ -34,6 +34,11 @@ type PublishingResponse = numberOfAffectedChanges: number; } } + | { + partialPublishFail: { + numberOfAffectedChanges: number; + } + } | { conflicts: Conflict[] } | { error: AnyError }; @@ -131,6 +136,8 @@ export function * watchPublishing({routes}: {routes: Routes}) { } } else if ('error' in result) { yield put(actions.CR.Publishing.fail(result.error)); + } else if ('partialPublishFail' in result) { + yield put(actions.CR.Publishing.partialFail()); } else { yield put(actions.CR.Publishing.fail(null)); } diff --git a/packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishAllConfirmationDialog.tsx b/packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishAllConfirmationDialog.tsx new file mode 100644 index 0000000000..c5714dc40f --- /dev/null +++ b/packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishAllConfirmationDialog.tsx @@ -0,0 +1,116 @@ +/* + * 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 7997c5916f..25dfa8036d 100644 --- a/packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishingDialog.tsx +++ b/packages/neos-ui/src/Containers/Modals/PublishingDialog/PublishingDialog.tsx @@ -23,6 +23,7 @@ import { import {ConfirmationDialog} from './ConfirmationDialog'; import {ProcessIndicator} from './ProcessIndicator'; import {ResultDialog} from './ResultDialog'; +import {PublishAllConfirmationDialog} from './PublishAllConfirmationDialog'; const { publishableNodesSelector, @@ -46,6 +47,7 @@ type PublishingDialogHandlers = { confirm: () => void; retry: () => void; acknowledge: () => void; + start: (mode: PublishingMode, scope: PublishingScope) => void; } type PublishingDialogProps = @@ -65,6 +67,11 @@ 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; } @@ -114,6 +121,19 @@ const PublishingDialog: React.FC = (props) => { onAcknowledge={handleAcknowledge} /> ); + case PublishingPhase.PARTIALLYFAILED: + return ( + + ); } }; @@ -160,5 +180,6 @@ 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 + acknowledge: (actions as any).CR.Publishing.acknowledge, + start: (actions as any).CR.Publishing.start })(PublishingDialog); From 7a22dc25b1cfc9033867a65333b566980384881c Mon Sep 17 00:00:00 2001 From: pKallert Date: Mon, 16 Dec 2024 13:28:08 +0100 Subject: [PATCH 2/4] 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; From fd81b4d00a3c5bbd0fac693fb622fadb424f7c55 Mon Sep 17 00:00:00 2001 From: pKallert Date: Fri, 20 Dec 2024 08:55:04 +0100 Subject: [PATCH 3/4] Feature: show confirmation dialogue for all resolution strategies --- packages/neos-ui-sagas/src/Publish/index.ts | 9 +---- packages/neos-ui-sagas/src/Sync/index.ts | 38 ++++++++++--------- .../ResolutionStrategyConfirmationDialog.tsx | 5 ++- .../SyncWorkspaceDialog.tsx | 28 ++++++-------- 4 files changed, 36 insertions(+), 44 deletions(-) diff --git a/packages/neos-ui-sagas/src/Publish/index.ts b/packages/neos-ui-sagas/src/Publish/index.ts index 91c57b5112..65d1a3c9fd 100644 --- a/packages/neos-ui-sagas/src/Publish/index.ts +++ b/packages/neos-ui-sagas/src/Publish/index.ts @@ -34,11 +34,6 @@ type PublishingResponse = numberOfAffectedChanges: number; } } - | { - partialPublishFail: { - conflicts: Conflict[]; - } - } | { conflicts: Conflict[], isPartialPublish: boolean } | { error: AnyError }; @@ -80,6 +75,7 @@ export function * watchPublishing({routes}: {routes: Routes}) { yield takeEvery(actionTypes.CR.Publishing.STARTED, function * publishingWorkflow(action: ReturnType) { const confirmed = yield * waitForConfirmation(); + if (!confirmed) { return; } @@ -97,7 +93,6 @@ export function * watchPublishing({routes}: {routes: Routes}) { const ancestorId: NodeContextPath = ancestorIdSelector ? yield select(ancestorIdSelector) : null; - function * attemptToPublishOrDiscard(): Generator { const result: PublishingResponse = scope === PublishingScope.ALL ? yield call(endpoint as any, workspaceName) @@ -136,8 +131,6 @@ export function * watchPublishing({routes}: {routes: Routes}) { } } else if ('error' in result) { yield put(actions.CR.Publishing.fail(result.error)); - } else if ('partialPublishFail' in result) { - yield put(actions.CR.Publishing.partialFail()); } else { yield put(actions.CR.Publishing.fail(null)); } diff --git a/packages/neos-ui-sagas/src/Sync/index.ts b/packages/neos-ui-sagas/src/Sync/index.ts index fccac1f665..71485c6c48 100644 --- a/packages/neos-ui-sagas/src/Sync/index.ts +++ b/packages/neos-ui-sagas/src/Sync/index.ts @@ -93,7 +93,7 @@ export const makeResolveConflicts = (deps: { syncPersonalWorkspace: ReturnType }) => { const discardAll = makeDiscardAll(deps); - const publishAll = makePublishAll(deps); + const publishAll = makePublishAll(); function * resolveConflicts(conflicts: Conflict[], isPartialPublish: boolean): any { while (true) { @@ -107,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); @@ -121,13 +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) { - yield * publishAll(); - return true; + if (yield * waitForResolutionConfirmation()) { + yield * publishAll(); + return true; + } + + continue; } } @@ -170,7 +176,7 @@ const makeDiscardAll = (deps: { PublishingMode.DISCARD, PublishingScope.ALL )); - + yield put(actions.CR.Publishing.confirm()); const {cancelled, failed}: { cancelled: null | ReturnType; failed: null | ReturnType; @@ -186,7 +192,7 @@ 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); } } @@ -194,16 +200,14 @@ const makeDiscardAll = (deps: { return discardAll; } -const makePublishAll = (deps: { - syncPersonalWorkspace: ReturnType; -}) => { +const makePublishAll = () => { function * publishAll() { yield put(actions.CR.Publishing.start( PublishingMode.PUBLISH, - PublishingScope.ALL + PublishingScope.SITE )); - - const {cancelled, failed}: { + yield put(actions.CR.Publishing.confirm()); + const {cancelled, failed, finished}: { cancelled: null | ReturnType; failed: null | ReturnType; finished: null | ReturnType; @@ -212,14 +216,12 @@ const makePublishAll = (deps: { 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); + yield put(actions.CR.Syncing.finish()); } } diff --git a/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ResolutionStrategyConfirmationDialog.tsx b/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ResolutionStrategyConfirmationDialog.tsx index 139f4d76ad..cf95638e20 100644 --- a/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ResolutionStrategyConfirmationDialog.tsx +++ b/packages/neos-ui/src/Containers/Modals/SyncWorkspaceDialog/ResolutionStrategyConfirmationDialog.tsx @@ -187,6 +187,7 @@ const DiscardAllConfirmationDialog: React.FC<{ } const PublishAllConfirmationDialog: React.FC<{ workspaceName: WorkspaceName; + baseWorkspaceName: WorkspaceName; totalNumberOfChangesInWorkspace: number; onCancelConflictResolution: () => void; onConfirmResolutionStrategy: () => void; @@ -214,7 +215,7 @@ const PublishAllConfirmationDialog: React.FC<{ onClick={props.onConfirmResolutionStrategy} className={style.button} > - + = (props) => { const handleRetry = React.useCallback(() => { props.retry(); }, []); - switch (props.syncingState?.process.phase) { case SyncingPhase.START: return ( @@ -124,21 +123,18 @@ const SyncWorkspaceDialog: React.FC = (props) => { /> ); case SyncingPhase.RESOLVING: - if (props.syncingState.process.strategy === ResolutionStrategy.FORCE) { - return ( - - ); - } - return null; + return ( + + ); case SyncingPhase.ERROR: case SyncingPhase.SUCCESS: return ( From cec189e04d670a615cae6a37cc5210b952b22fa2 Mon Sep 17 00:00:00 2001 From: pKallert Date: Thu, 2 Jan 2025 10:25:00 +0100 Subject: [PATCH 4/4] Feature: add IDLE status for syncing status --- packages/neos-ui-redux-store/src/CR/Syncing/index.ts | 4 +++- packages/neos-ui-sagas/src/Sync/index.ts | 11 ++++++----- 2 files changed, 9 insertions(+), 6 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 c6f1c7b853..623ebf0ff5 100644 --- a/packages/neos-ui-redux-store/src/CR/Syncing/index.ts +++ b/packages/neos-ui-redux-store/src/CR/Syncing/index.ts @@ -18,6 +18,7 @@ export enum SyncingPhase { ONGOING, CONFLICT, RESOLVING, + IDLE, ERROR, SUCCESS } @@ -65,6 +66,7 @@ export type State = null | { strategy: ResolutionStrategy; conflicts: Conflict[]; } + | { phase: SyncingPhase.IDLE } | { phase: SyncingPhase.ERROR; error: null | AnyError; @@ -247,7 +249,7 @@ export const reducer = (state: State = defaultState, action: Action): State => { return { ...state, process: { - phase: SyncingPhase.ONGOING + phase: SyncingPhase.IDLE } }; case actionTypes.FAILED: diff --git a/packages/neos-ui-sagas/src/Sync/index.ts b/packages/neos-ui-sagas/src/Sync/index.ts index 71485c6c48..54de145c8c 100644 --- a/packages/neos-ui-sagas/src/Sync/index.ts +++ b/packages/neos-ui-sagas/src/Sync/index.ts @@ -93,7 +93,7 @@ export const makeResolveConflicts = (deps: { syncPersonalWorkspace: ReturnType }) => { const discardAll = makeDiscardAll(deps); - const publishAll = makePublishAll(); + const publishAll = makePublishAll(deps); function * resolveConflicts(conflicts: Conflict[], isPartialPublish: boolean): any { while (true) { @@ -132,7 +132,6 @@ export const makeResolveConflicts = (deps: { yield * publishAll(); return true; } - continue; } } @@ -186,7 +185,6 @@ const makeDiscardAll = (deps: { failed: take(actionTypes.CR.Publishing.FAILED), finished: take(actionTypes.CR.Publishing.FINISHED) }); - if (cancelled) { yield put(actions.CR.Syncing.cancelResolution()); } else if (failed) { @@ -200,14 +198,16 @@ const makeDiscardAll = (deps: { return discardAll; } -const makePublishAll = () => { +const makePublishAll = (deps: { + syncPersonalWorkspace: ReturnType; +}) => { function * publishAll() { yield put(actions.CR.Publishing.start( PublishingMode.PUBLISH, PublishingScope.SITE )); yield put(actions.CR.Publishing.confirm()); - const {cancelled, failed, finished}: { + const {cancelled, failed}: { cancelled: null | ReturnType; failed: null | ReturnType; finished: null | ReturnType; @@ -222,6 +222,7 @@ const makePublishAll = () => { yield put(actions.CR.Syncing.finish()); } else { yield put(actions.CR.Syncing.finish()); + yield * deps.syncPersonalWorkspace(false); } }