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

Open
wants to merge 14 commits into
base: 9.0
Choose a base branch
from
Open
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 @@ -67,19 +68,20 @@ public function handle(
numberOfAffectedChanges: $publishingResult->numberOfPublishedChanges,
baseWorkspaceName: $workspace?->baseWorkspaceName?->value
);
} catch (NodeAggregateCurrentlyDoesNotExist $e) {
throw new \RuntimeException(
$this->getLabel('NodeNotPublishedMissingParentNode'),
1705053430,
$e
} catch (WorkspaceRebaseFailed $e) {
$conflictsFactory = new ConflictsFactory(
contentRepository: $this->contentRepositoryRegistry
->get($command->contentRepositoryId),
nodeLabelGenerator: $this->nodeLabelGenerator,
workspaceName: $command->workspaceName,
preferredDimensionSpacePoint: $command->preferredDimensionSpacePoint
);
} catch (NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint $e) {
throw new \RuntimeException(
$this->getLabel('NodeNotPublishedParentNodeNotInCurrentDimension'),
1705053432,
$e

return new ConflictsOccurred(
conflicts: $conflictsFactory->fromWorkspaceRebaseFailed($e),
isPartialPublish: false
);
} catch (WorkspaceRebaseFailed $e) {
} catch (PartialWorkspaceRebaseFailed $e) {
$conflictsFactory = new ConflictsFactory(
contentRepository: $this->contentRepositoryRegistry
->get($command->contentRepositoryId),
Expand All @@ -89,7 +91,20 @@ public function handle(
);

return new ConflictsOccurred(
conflicts: $conflictsFactory->fromWorkspaceRebaseFailed($e)
conflicts: $conflictsFactory->fromPartialWorkspaceRebaseFailed($e),
isPartialPublish: true
);
} catch (NodeAggregateCurrentlyDoesNotExist $e) {
throw new \RuntimeException(
$this->getLabel('NodeNotPublishedMissingParentNode'),
1705053430,
$e
);
} catch (NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint $e) {
throw new \RuntimeException(
$this->getLabel('NodeNotPublishedParentNodeNotInCurrentDimension'),
1705053432,
$e
);
}
}
Expand Down
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
13 changes: 13 additions & 0 deletions Resources/Private/Translations/en/PublishingDialog.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,19 @@
<trans-unit id="publish.document.success.acknowledge" xml:space="preserve">
<source>OK</source>
</trans-unit>
<trans-unit id="publishAll.document.confirmation.title" xml:space="preserve">
<source>Could not publish changes in document "{scopeTitle}"</source>
</trans-unit>
<trans-unit id="publishAll.document.confirmation.message" xml:space="preserve">
<source>There seem to be dependencies to other documents.
Do you want to instead publish all changes in site to workspace "{targetWorkspaceName}"? Be careful: This cannot be undone!</source>
</trans-unit>
<trans-unit id="publishAll.document.confirmation.cancel" xml:space="preserve">
<source>No, cancel</source>
</trans-unit>
<trans-unit id="publishAll.document.confirmation.confirm" xml:space="preserve">
<source>Yes, publish all changes in site</source>
</trans-unit>
<trans-unit id="discard.all.confirmation.title" xml:space="preserve">
<source>Discard all changes in workspace "{scopeTitle}"</source>
</trans-unit>
Expand Down
16 changes: 16 additions & 0 deletions packages/neos-ui-redux-store/src/CR/Publishing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export enum PublishingPhase {
START,
ONGOING,
CONFLICTS,
PARTIAL_PUBLISH_CONFLICTS,
SUCCESS,
ERROR
}
Expand All @@ -40,6 +41,7 @@ export type State = null | {
autoConfirmed: boolean
}
| { phase: PublishingPhase.CONFLICTS }
| { phase: PublishingPhase.PARTIAL_PUBLISH_CONFLICTS }
| {
phase: PublishingPhase.ERROR;
error: null | AnyError;
Expand All @@ -58,6 +60,7 @@ export enum actionTypes {
CONFIRMED = '@neos/neos-ui/CR/Publishing/CONFIRMED',
CONFLICTS_OCCURRED = '@neos/neos-ui/CR/Publishing/CONFLICTS_OCCURRED',
CONFLICTS_RESOLVED = '@neos/neos-ui/CR/Publishing/CONFLICTS_RESOLVED',
PARTIAL_PUBLISH_CONFLICTS_OCCURRED = '@neos/neos-ui/CR/Publishing/PARTIAL_PUBLISH_CONFLICTS_OCCURRED',
FAILED = '@neos/neos-ui/CR/Publishing/FAILED',
RETRIED = '@neos/neos-ui/CR/Publishing/RETRIED',
SUCEEDED = '@neos/neos-ui/CR/Publishing/SUCEEDED',
Expand Down Expand Up @@ -91,6 +94,11 @@ const conflicts = () => createAction(actionTypes.CONFLICTS_OCCURRED);
*/
const resolveConflicts = () => createAction(actionTypes.CONFLICTS_RESOLVED);

/**
* Signal that partial publish conflict has occurred during the publish/discard operation
*/
const partialPublishConflict = () => createAction(actionTypes.PARTIAL_PUBLISH_CONFLICTS_OCCURRED);

/**
* Signal that the ongoing publish/discard workflow has failed
*/
Expand Down Expand Up @@ -127,6 +135,7 @@ export const actions = {
confirm,
conflicts,
resolveConflicts,
partialPublishConflict,
fail,
retry,
succeed,
Expand Down Expand Up @@ -191,6 +200,13 @@ export const reducer = (state: State = defaultState, action: Action): State => {
autoConfirmed: false
}
};
case actionTypes.PARTIAL_PUBLISH_CONFLICTS_OCCURRED:
return {
...state,
process: {
phase: PublishingPhase.PARTIAL_PUBLISH_CONFLICTS
}
};
case actionTypes.FAILED:
return {
...state,
Expand Down
3 changes: 2 additions & 1 deletion 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
8 changes: 6 additions & 2 deletions packages/neos-ui-sagas/src/Publish/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type PublishingResponse =
numberOfAffectedChanges: number;
}
}
| { conflicts: Conflict[] }
| { conflicts: Conflict[], isPartialPublish: boolean }
| { error: AnyError };

const PUBLISH_SUCCESS_TRANSLATIONS = {
Expand All @@ -51,6 +51,7 @@ const PUBLISH_SUCCESS_TRANSLATIONS = {
id: 'Neos.Neos.Ui:PublishingDialog:publish.document.success.message',
fallback: '{numberOfChanges} change(s) in document "{scopeTitle}" were sucessfully published to workspace "{targetWorkspaceName}".'
}

}

export function * watchPublishing({routes}: {routes: Routes}) {
Expand Down Expand Up @@ -143,8 +144,11 @@ export function * watchPublishing({routes}: {routes: Routes}) {
yield put(actions.CR.Publishing.finish());
}
yield * reloadAfterPublishing();
} else if ('conflicts' in result) {
} else if ('conflicts' in result && result.isPartialPublish) {
yield put(actions.CR.Publishing.partialPublishConflict());
} else if ('conflicts' in result && !result.isPartialPublish) {
yield put(actions.CR.Publishing.conflicts());

const conflictsWereResolved: boolean =
yield * resolveConflicts(result.conflicts);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ const Changes: React.FC<{
/>
);
}
if (props.phase === PublishingPhase.PARTIAL_PUBLISH_CONFLICTS) {
return (
<div className={style.diagram__process__changes}>
<Icon
className={cx(
style.diagram__process__icon,
style['diagram__process__icon--warn'],
)}
icon="question"
/>
<span className={style.diagram__process__changes__number}>
{props.numberOfChanges}
</span>
</div>
);
}

if (props.phase === PublishingPhase.SUCCESS) {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* 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';

const PublishAllDialogVariants = {
id: 'neos-PublishAllDialog',
style: 'warn',
icon: {
title: 'share-square-o',
confirm: 'share-square-o'
},
label: {
title: {
id: 'Neos.Neos.Ui:PublishingDialog:publishAll.document.confirmation.title',
fallback: (props: { scopeTitle: string; }) =>
`Could not publish changes in document "${props.scopeTitle}"`
},
message: {
id: 'Neos.Neos.Ui:PublishingDialog:publishAll.document.confirmation.message',
fallback: (props: { scopeTitle: string; sourceWorkspaceName: string; targetWorkspaceName: null | string; }) =>
`There seem to be dependencies to other documents.
Do you want to instead publish all changes in site to workspace "${props.targetWorkspaceName}"? Be careful: This cannot be undone!`
},
cancel: {
id: 'Neos.Neos.Ui:PublishingDialog:publishAll.document.confirmation.cancel',
fallback: 'No, cancel'
},
confirm: {
id: 'Neos.Neos.Ui:PublishingDialog:publishAll.document.confirmation.confirm',
fallback: 'Yes, publish all changes in site'
}
}
} as const;

type PublishAllDialogProps = {
mode: PublishingMode;
scope: PublishingScope;
scopeTitle: string;
sourceWorkspaceName: string;
targetWorkspaceName: null | string;
numberOfChanges: number;
numberOfSiteChanges: number;
onAbort: () => void;
onConfirm: () => void;
}

export const PublishAllDialog: React.FC<PublishAllDialogProps> = (props) => {
const variant = PublishAllDialogVariants;

return (
<Dialog
actions={[
<Button
id={`${variant.id}-Cancel`}
key="cancel"
style="lighter"
hoverStyle="brand"
onClick={props.onAbort}
>
<I18n {...variant.label.cancel} />
</Button>,
<Button
id={`${variant.id}-Confirm`}
key="confirm"
style={variant.style}
hoverStyle={variant.style}
onClick={props.onConfirm}
>
<Icon icon={variant.icon.confirm} className={style.buttonIcon} />
<I18n {...variant.label.confirm} />
</Button>
]}
title={<div>
<Icon icon={variant.icon.title} />
<span className={style.modalTitle}>
<I18n
id={variant.label.title.id}
params={props}
fallback={variant.label.title.fallback(props)}
/>
</span>
</div>}
onRequestClose={props.onAbort}
type={variant.style}
isOpen
autoFocus
theme={undefined as any}
style={undefined as any}
>
<div className={style.modalContents}>
<Diagram
phase={PublishingPhase.PARTIAL_PUBLISH_CONFLICTS}
sourceWorkspaceName={props.sourceWorkspaceName}
targetWorkspaceName={props.targetWorkspaceName}
numberOfChanges={props.numberOfSiteChanges}
/>
<I18n
id={variant.label.message.id}
params={props}
fallback={variant.label.message.fallback(props)}
/>
</div>
</Dialog>
);
};
Loading
Loading