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);