Skip to content

Commit

Permalink
Merge pull request #5301 from mhsdesign/task/schnappsidee-rebase-and-…
Browse files Browse the repository at this point in the history
…partial-publish-in-memory-event-stream

!!! FEATURE: Publishing Version 3
  • Loading branch information
mhsdesign authored Oct 28, 2024
2 parents 39f19d2 + 3761fc2 commit 73137e3
Show file tree
Hide file tree
Showing 71 changed files with 1,855 additions and 1,073 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,6 @@ public function getContentGraph(WorkspaceName $workspaceName): ContentGraph
return new ContentGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $currentContentStreamId);
}

public function buildContentGraph(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraph
{
return new ContentGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNames, $workspaceName, $contentStreamId);
}

public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace
{
$workspaceQuery = $this->getBasicWorkspaceQuery()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Projection\NodeRelationAnchorPoint;
use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\DimensionSpacePointsRepository;
use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\ProjectionContentGraph;
use Neos\ContentRepository\Core\EventStore\InitiatingEventMetadata;
use Neos\ContentRepository\Core\Projection\ContentGraph\ContentGraphReadModelInterface;
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePointSet;
Expand Down Expand Up @@ -241,6 +242,21 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void
}
}

public function inSimulation(\Closure $fn): mixed
{
if ($this->dbal->isTransactionActive()) {
throw new \RuntimeException(sprintf('Invoking %s is not allowed to be invoked recursively. Current transaction nesting %d.', __FUNCTION__, $this->dbal->getTransactionNestingLevel()));
}
$this->dbal->beginTransaction();
$this->dbal->setRollbackOnly();
try {
return $fn();
} finally {
// unsets rollback only flag and allows the connection to work regular again
$this->dbal->rollBack();
}
}

private function whenContentStreamWasClosed(ContentStreamWasClosed $event): void
{
$this->updateContentStreamStatus($event->contentStreamId, ContentStreamStatus::CLOSED);
Expand Down Expand Up @@ -748,7 +764,6 @@ private function whenWorkspaceWasPartiallyDiscarded(WorkspaceWasPartiallyDiscard

private function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublished $event): void
{
// TODO: How do we test this method? – It's hard to design a BDD testcase that fails if this method is commented out...
$this->updateWorkspaceContentStreamId($event->sourceWorkspaceName, $event->newSourceContentStreamId);

// the new content stream is in use now
Expand All @@ -760,7 +775,6 @@ private function whenWorkspaceWasPartiallyPublished(WorkspaceWasPartiallyPublish

private function whenWorkspaceWasPublished(WorkspaceWasPublished $event): void
{
// TODO: How do we test this method? – It's hard to design a BDD testcase that fails if this method is commented out...
$this->updateWorkspaceContentStreamId($event->sourceWorkspaceName, $event->newSourceContentStreamId);

// the new content stream is in use now
Expand Down Expand Up @@ -909,12 +923,14 @@ private function copyReferenceRelations(

private static function initiatingDateTime(EventEnvelope $eventEnvelope): \DateTimeImmutable
{
$initiatingTimestamp = $eventEnvelope->event->metadata?->get('initiatingTimestamp');
$result = $initiatingTimestamp !== null ? \DateTimeImmutable::createFromFormat(\DateTimeInterface::ATOM, $initiatingTimestamp) : $eventEnvelope->recordedAt;
if (!$result instanceof \DateTimeImmutable) {
if ($eventEnvelope->event->metadata?->has(InitiatingEventMetadata::INITIATING_TIMESTAMP) !== true) {
return $eventEnvelope->recordedAt;
}
$initiatingTimestamp = InitiatingEventMetadata::getInitiatingTimestamp($eventEnvelope->event->metadata);
if ($initiatingTimestamp === null) {
throw new \RuntimeException(sprintf('Failed to extract initiating timestamp from event "%s"', $eventEnvelope->event->id->value), 1678902291);
}
return $result;
return $initiatingTimestamp;
}

private function createNodeWithHierarchy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@ public function getContentGraph(WorkspaceName $workspaceName): ContentGraphInter
return new ContentHyperGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNamePrefix, $workspaceName, $contentStreamId);
}

public function buildContentGraph(WorkspaceName $workspaceName, ContentStreamId $contentStreamId): ContentGraphInterface
{
return new ContentHyperGraph($this->dbal, $this->nodeFactory, $this->contentRepositoryId, $this->nodeTypeManager, $this->tableNamePrefix, $workspaceName, $contentStreamId);
}

public function findWorkspaceByName(WorkspaceName $workspaceName): ?Workspace
{
// TODO: Implement findWorkspaceByName() method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,21 @@ public function apply(EventInterface $event, EventEnvelope $eventEnvelope): void
};
}

public function inSimulation(\Closure $fn): mixed
{
if ($this->dbal->isTransactionActive()) {
throw new \RuntimeException(sprintf('Invoking %s is not allowed to be invoked recursively. Current transaction nesting %d.', __FUNCTION__, $this->dbal->getTransactionNestingLevel()));
}
$this->dbal->beginTransaction();
$this->dbal->setRollbackOnly();
try {
return $fn();
} finally {
// unsets rollback only flag and allows the connection to work regular again
$this->dbal->rollBack();
}
}

public function getCheckpointStorage(): DbalCheckpointStorage
{
return $this->checkpointStorage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,19 @@ Feature: Behavior of Node timestamp properties "created", "originalCreated", "la
And I expect the node "b" to have the following timestamps:
| created | originalCreated | lastModified | originalLastModified |
| 2023-03-16 15:00:00 | 2023-03-16 12:00:00 | | |
Scenario: Original created when rebasing and partially publishing nodes
And I am in workspace "user-test" and dimension space point {"language":"de"}
Then I expect the node "a" to have the following timestamps:
| created | originalCreated | lastModified | originalLastModified |
| 2023-03-16 12:00:00 | 2023-03-16 12:00:00 | null | null |
Given the current date and time is "2023-03-16T14:00:00+01:00"
When the command RebaseWorkspace is executed with payload:
| Key | Value |
| workspaceName | "user-test" |
| rebaseErrorHandlingStrategy | "force" |
Then I expect the node "a" to have the following timestamps:
| created | originalCreated | lastModified | originalLastModified |
| 2023-03-16 14:00:00 | 2023-03-16 12:00:00 | null | null |
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ Feature: Workspace discarding - complex chained functionality
| workspaceName | "user-ws" |
| nodesToDiscard | [{"workspaceName": "user-ws", "dimensionSpacePoint": {"language": "en"}, "nodeAggregateId": "sir-david-nodenborough"}, {"workspaceName": "user-ws", "dimensionSpacePoint": {"language": "en"}, "nodeAggregateId": "sir-david-nodenborough"}] |
| newContentStreamId | "user-cs-id-rebased" |
Then the last command should have thrown an exception of type "NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint"
Then the last command should have thrown the WorkspaceRebaseFailed exception with:
| SequenceNumber | Command | Exception |
| 11 | CreateNodeVariant | NodeAggregateDoesCurrentlyNotCoverDimensionSpacePoint |

When the command DiscardWorkspace is executed with payload:
| Key | Value |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,44 +29,38 @@ Feature: Discard individual nodes (basics)
| Key | Value |
| workspaceName | "live" |
| newContentStreamId | "cs-identifier" |
And I am in workspace "live"
And I am in workspace "live" and dimension space point {}
And the command CreateRootNodeAggregateWithNode is executed with payload:
| Key | Value |
| nodeAggregateId | "lady-eleonode-rootford" |
| nodeTypeName | "Neos.ContentRepository:Root" |
And the event NodeAggregateWithNodeWasCreated was published with payload:
And the command CreateNodeAggregateWithNode is executed with payload:
| Key | Value |
| workspaceName | "live" |
| contentStreamId | "cs-identifier" |
| nodeAggregateId | "sir-david-nodenborough" |
| nodeTypeName | "Neos.ContentRepository.Testing:Content" |
| originDimensionSpacePoint | {} |
| coveredDimensionSpacePoints | [{}] |
| parentNodeAggregateId | "lady-eleonode-rootford" |
| initialPropertyValues | {"text": {"type": "string", "value": "Initial t1"}} |
| nodeAggregateClassification | "regular" |
And the event NodeAggregateWithNodeWasCreated was published with payload:
| initialPropertyValues | {"text": "Initial t1"} |
And the command CreateNodeAggregateWithNode is executed with payload:
| Key | Value |
| workspaceName | "live" |
| contentStreamId | "cs-identifier" |
| nodeAggregateId | "nody-mc-nodeface" |
| nodeTypeName | "Neos.ContentRepository.Testing:Content" |
| originDimensionSpacePoint | {} |
| coveredDimensionSpacePoints | [{}] |
| parentNodeAggregateId | "sir-david-nodenborough" |
| initialPropertyValues | {"text": {"type": "string", "value": "Initial t2"}} |
| nodeAggregateClassification | "regular" |
And the event NodeAggregateWithNodeWasCreated was published with payload:
| initialPropertyValues | {"text": "Initial t2"} |
And the command CreateNodeAggregateWithNode is executed with payload:
| Key | Value |
| workspaceName | "live" |
| contentStreamId | "cs-identifier" |
| nodeAggregateId | "sir-nodeward-nodington-iii" |
| nodeTypeName | "Neos.ContentRepository.Testing:Image" |
| originDimensionSpacePoint | {} |
| coveredDimensionSpacePoints | [{}] |
| parentNodeAggregateId | "lady-eleonode-rootford" |
| initialPropertyValues | {"image": {"type": "string", "value": "Initial image"}} |
| nodeAggregateClassification | "regular" |
| initialPropertyValues | {"image": "Initial image"} |
And the command CreateNodeAggregateWithNode is executed with payload:
| Key | Value |
| workspaceName | "live" |
| nodeAggregateId | "sir-unchanged" |
| nodeTypeName | "Neos.ContentRepository.Testing:Image" |
| parentNodeAggregateId | "lady-eleonode-rootford" |
# Create user workspace
And the command CreateWorkspace is executed with payload:
Expand Down Expand Up @@ -104,7 +98,7 @@ Feature: Discard individual nodes (basics)
| workspaceName | "user-test" |
| nodesToDiscard | [{"workspaceName": "user-test", "dimensionSpacePoint": {}, "nodeAggregateId": "sir-nodeward-nodington-iii"}] |
| newContentStreamId | "user-cs-identifier-new" |
Then I expect the content stream "user-cs-identifier" to not exist
When I am in workspace "user-test" and dimension space point {}
Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier-new;sir-david-nodenborough;{}
Expand All @@ -119,35 +113,60 @@ Feature: Discard individual nodes (basics)
And I expect this node to have the following properties:
| Key | Value |
| image | "Initial image" |
Scenario: It is possible to discard no node
Scenario: Discard no node, non existing ones or unchanged nodes is a no-op
# no node
When the command DiscardIndividualNodesFromWorkspace is executed with payload:
| Key | Value |
| workspaceName | "user-test" |
| nodesToDiscard | [] |
| newContentStreamId | "user-cs-identifier-new" |
Then I expect the content stream "user-cs-identifier-new" to not exist
# unchanged or non existing nodes
When the command DiscardIndividualNodesFromWorkspace is executed with payload:
| Key | Value |
| workspaceName | "user-test" |
| nodesToDiscard | [{"dimensionSpacePoint": {}, "nodeAggregateId": "non-existing-node"}, {"dimensionSpacePoint": {}, "nodeAggregateId": "sir-unchanged"}] |
| newContentStreamId | "user-cs-identifier-new-two" |
# all nodes are still on the original user cs
When I am in workspace "user-test" and dimension space point {}
Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier-new;sir-david-nodenborough;{}
Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier;sir-david-nodenborough;{}
And I expect this node to have the following properties:
| Key | Value |
| text | "Modified t1" |
Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-identifier-new;nody-mc-nodeface;{}
Then I expect node aggregate identifier "nody-mc-nodeface" to lead to node user-cs-identifier;nody-mc-nodeface;{}
And I expect this node to have the following properties:
| Key | Value |
| text | "Modified t2" |
Then I expect node aggregate identifier "sir-nodeward-nodington-iii" to lead to node user-cs-identifier-new;sir-nodeward-nodington-iii;{}
Then I expect node aggregate identifier "sir-nodeward-nodington-iii" to lead to node user-cs-identifier;sir-nodeward-nodington-iii;{}
And I expect this node to have the following properties:
| Key | Value |
| image | "Modified image" |
# assert that content stream is still open by writing to it:
And the command SetNodeProperties is executed with payload:
| Key | Value |
| workspaceName | "user-test" |
| nodeAggregateId | "sir-nodeward-nodington-iii" |
| originDimensionSpacePoint | {} |
| propertyValues | {"image": "Bla bli blub"} |
Scenario: It is possible to discard all nodes
When the command DiscardIndividualNodesFromWorkspace is executed with payload:
| Key | Value |
| workspaceName | "user-test" |
| nodesToDiscard | [{"workspaceName": "user-test", "dimensionSpacePoint": {}, "nodeAggregateId": "sir-david-nodenborough"}, {"workspaceName": "user-test", "dimensionSpacePoint": {}, "nodeAggregateId": "nody-mc-nodeface"}, {"workspaceName": "user-test", "dimensionSpacePoint": {}, "nodeAggregateId": "sir-nodeward-nodington-iii"}] |
| newContentStreamId | "user-cs-identifier-new" |
# when discarding all nodes we expect a full discard via WorkspaceWasDiscarded
Then I expect exactly 2 events to be published on stream with prefix "Workspace:user-test"
And event at index 1 is of type "WorkspaceWasDiscarded" with payload:
| Key | Expected |
| workspaceName | "user-test" |
| newContentStreamId | "user-cs-identifier-new" |
| previousContentStreamId | "user-cs-identifier" |
When I am in workspace "user-test" and dimension space point {}
Then I expect node aggregate identifier "sir-david-nodenborough" to lead to node user-cs-identifier-new;sir-david-nodenborough;{}
And I expect this node to have the following properties:
Expand Down
Loading

0 comments on commit 73137e3

Please sign in to comment.