Skip to content

Commit 3a5b6c5

Browse files
authored
Merge pull request #5307 from neos/feature/4448-site-import
!!! FEATURE: High level Neos site import `site:importAll`
2 parents 85b6a4d + 5696f36 commit 3a5b6c5

File tree

58 files changed

+1940
-1454
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1940
-1454
lines changed

Neos.ContentRepository.Export/Tests/Behavior/Features/Bootstrap/CrImportExportTrait.php

Lines changed: 174 additions & 79 deletions
Large diffs are not rendered by default.

Neos.ContentRepository.Export/Tests/Behavior/Features/Export/Export.feature renamed to Neos.ContentRepository.Export/Tests/Behavior/Features/EventExportProcessor.feature

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
@contentrepository
2-
Feature: As a user of the CR I want to export the event stream
2+
Feature: As a user of the CR I want to export the event stream using the EventExportProcessor
33

44
Background:
55
Given using the following content dimensions:
@@ -12,9 +12,9 @@ Feature: As a user of the CR I want to export the event stream
1212
And using identifier "default", I define a content repository
1313
And I am in content repository "default"
1414
And the command CreateRootWorkspace is executed with payload:
15-
| Key | Value |
16-
| workspaceName | "live" |
17-
| newContentStreamId | "cs-identifier" |
15+
| Key | Value |
16+
| workspaceName | "live" |
17+
| newContentStreamId | "cs-identifier" |
1818
And I am in workspace "live"
1919
And the command CreateRootNodeAggregateWithNode is executed with payload:
2020
| Key | Value |
@@ -37,7 +37,7 @@ Feature: As a user of the CR I want to export the event stream
3737
When the events are exported
3838
Then I expect the following jsonl:
3939
"""
40-
{"identifier":"random-event-uuid","type":"RootNodeAggregateWithNodeWasCreated","payload":{"workspaceName":"live","contentStreamId":"cs-identifier","nodeAggregateId":"lady-eleonode-rootford","nodeTypeName":"Neos.ContentRepository:Root","coveredDimensionSpacePoints":[{"language":"de"},{"language":"gsw"},{"language":"fr"}],"nodeAggregateClassification":"root"},"metadata":{"commandClass":"Neos\\ContentRepository\\Core\\Feature\\RootNodeCreation\\Command\\CreateRootNodeAggregateWithNode","commandPayload":{"workspaceName":"live","nodeAggregateId":"lady-eleonode-rootford","nodeTypeName":"Neos.ContentRepository:Root","tetheredDescendantNodeAggregateIds":[]},"initiatingUserId":"system","initiatingTimestamp":"random-time"}}
41-
{"identifier":"random-event-uuid","type":"NodeAggregateWithNodeWasCreated","payload":{"workspaceName":"live","contentStreamId":"cs-identifier","nodeAggregateId":"nody-mc-nodeface","nodeTypeName":"Neos.ContentRepository.Testing:Document","originDimensionSpacePoint":{"language":"de"},"succeedingSiblingsForCoverage":[{"dimensionSpacePoint":{"language":"de"},"nodeAggregateId":null},{"dimensionSpacePoint":{"language":"gsw"},"nodeAggregateId":null},{"dimensionSpacePoint":{"language":"fr"},"nodeAggregateId":null}],"parentNodeAggregateId":"lady-eleonode-rootford","nodeName":"child-document","initialPropertyValues":[],"nodeAggregateClassification":"regular","nodeReferences":[]},"metadata":{"initiatingTimestamp":"random-time"}}
40+
{"identifier":"random-event-uuid","type":"RootNodeAggregateWithNodeWasCreated","payload":{"nodeAggregateId":"lady-eleonode-rootford","nodeTypeName":"Neos.ContentRepository:Root","coveredDimensionSpacePoints":[{"language":"de"},{"language":"gsw"},{"language":"fr"}],"nodeAggregateClassification":"root"},"metadata":{"commandClass":"Neos\\ContentRepository\\Core\\Feature\\RootNodeCreation\\Command\\CreateRootNodeAggregateWithNode","commandPayload":{"workspaceName":"live","nodeAggregateId":"lady-eleonode-rootford","nodeTypeName":"Neos.ContentRepository:Root","tetheredDescendantNodeAggregateIds":[]},"initiatingUserId":"system","initiatingTimestamp":"random-time"}}
41+
{"identifier":"random-event-uuid","type":"NodeAggregateWithNodeWasCreated","payload":{"nodeAggregateId":"nody-mc-nodeface","nodeTypeName":"Neos.ContentRepository.Testing:Document","originDimensionSpacePoint":{"language":"de"},"succeedingSiblingsForCoverage":[{"dimensionSpacePoint":{"language":"de"},"nodeAggregateId":null},{"dimensionSpacePoint":{"language":"gsw"},"nodeAggregateId":null},{"dimensionSpacePoint":{"language":"fr"},"nodeAggregateId":null}],"parentNodeAggregateId":"lady-eleonode-rootford","nodeName":"child-document","initialPropertyValues":[],"nodeAggregateClassification":"regular","nodeReferences":[]},"metadata":{"initiatingTimestamp":"random-time"}}
4242
4343
"""

Neos.ContentRepository.Export/Tests/Behavior/Features/Import/Import.feature renamed to Neos.ContentRepository.Export/Tests/Behavior/Features/EventStoreImportProcessor.feature

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
@contentrepository
2-
Feature: As a user of the CR I want to export the event stream
2+
Feature: As a user of the CR I want to import events using the EventStoreImportProcessor
33

44
Background:
55
Given using no content dimensions
@@ -11,56 +11,59 @@ Feature: As a user of the CR I want to export the event stream
1111
"""
1212
And using identifier "default", I define a content repository
1313
And I am in content repository "default"
14+
And the command CreateRootWorkspace is executed with payload:
15+
| Key | Value |
16+
| workspaceName | "live" |
17+
| newContentStreamId | "cs-identifier" |
18+
And I am in workspace "live"
1419

1520
Scenario: Import the event stream into a specific content stream
16-
Then I expect exactly 0 events to be published on stream with prefix "ContentStream:cs-identifier"
1721
Given using the following events.jsonl:
1822
"""
1923
{"identifier":"9f64c281-e5b0-48d9-900b-288a8faf92a9","type":"RootNodeAggregateWithNodeWasCreated","payload":{"workspaceName":"workspace-name","contentStreamId":"cs-imported-identifier","nodeAggregateId":"acme-site-sites","nodeTypeName":"Neos.Neos:Sites","coveredDimensionSpacePoints":[[]],"nodeAggregateClassification":"root"},"metadata":[]}
2024
{"identifier":"1640ebbf-7ffe-4526-b0f4-7575cefabfab","type":"NodeAggregateWithNodeWasCreated","payload":{"workspaceName":"workspace-name","contentStreamId":"cs-imported-identifier","nodeAggregateId":"acme-site","nodeTypeName":"Vendor.Site:HomePage","originDimensionSpacePoint":[],"succeedingSiblingsForCoverage":[{"dimensionSpacePoint":[],"nodeAggregateId":null}],"parentNodeAggregateId":"acme-site-sites","nodeName":"acme-site","initialPropertyValues":{"title":{"value":"My Site","type":"string"},"uriPathSegment":{"value":"my-site","type":"string"}},"nodeAggregateClassification":"regular"},"metadata":[]}
2125
"""
22-
And I import the events.jsonl into "cs-identifier"
26+
And I import the events.jsonl into workspace "live"
2327
Then I expect exactly 3 events to be published on stream with prefix "ContentStream:cs-identifier"
2428
And event at index 0 is of type "ContentStreamWasCreated" with payload:
2529
| Key | Expected |
2630
| contentStreamId | "cs-identifier" |
2731
And event at index 1 is of type "RootNodeAggregateWithNodeWasCreated" with payload:
2832
| Key | Expected |
29-
| workspaceName | "workspace-name" |
33+
| workspaceName | "live" |
3034
| contentStreamId | "cs-identifier" |
3135
| nodeAggregateId | "acme-site-sites" |
3236
| nodeTypeName | "Neos.Neos:Sites" |
3337
And event at index 2 is of type "NodeAggregateWithNodeWasCreated" with payload:
3438
| Key | Expected |
35-
| workspaceName | "workspace-name" |
39+
| workspaceName | "live" |
3640
| contentStreamId | "cs-identifier" |
3741
| nodeAggregateId | "acme-site" |
3842
| nodeTypeName | "Vendor.Site:HomePage" |
3943

4044
Scenario: Import the event stream
41-
Then I expect exactly 0 events to be published on stream with prefix "ContentStream:cs-imported-identifier"
4245
Given using the following events.jsonl:
4346
"""
4447
{"identifier":"9f64c281-e5b0-48d9-900b-288a8faf92a9","type":"RootNodeAggregateWithNodeWasCreated","payload":{"workspaceName":"workspace-name","contentStreamId":"cs-imported-identifier","nodeAggregateId":"acme-site-sites","nodeTypeName":"Neos.Neos:Sites","coveredDimensionSpacePoints":[[]],"nodeAggregateClassification":"root"},"metadata":[]}
4548
{"identifier":"1640ebbf-7ffe-4526-b0f4-7575cefabfab","type":"NodeAggregateWithNodeWasCreated","payload":{"workspaceName":"workspace-name","contentStreamId":"cs-imported-identifier","nodeAggregateId":"acme-site","nodeTypeName":"Vendor.Site:HomePage","originDimensionSpacePoint":[],"succeedingSiblingsForCoverage":[{"dimensionSpacePoint":[],"nodeAggregateId":null}],"parentNodeAggregateId":"acme-site-sites","nodeName":"acme-site","initialPropertyValues":{"title":{"value":"My Site","type":"string"},"uriPathSegment":{"value":"my-site","type":"string"}},"nodeAggregateClassification":"regular"},"metadata":[]}
4649
"""
4750
And I import the events.jsonl
48-
Then I expect exactly 3 events to be published on stream with prefix "ContentStream:cs-imported-identifier"
51+
Then I expect exactly 3 events to be published on stream with prefix "ContentStream:cs-identifier"
4952
And event at index 0 is of type "ContentStreamWasCreated" with payload:
50-
| Key | Expected |
51-
| contentStreamId | "cs-imported-identifier" |
53+
| Key | Expected |
54+
| contentStreamId | "cs-identifier" |
5255
And event at index 1 is of type "RootNodeAggregateWithNodeWasCreated" with payload:
53-
| Key | Expected |
54-
| workspaceName | "workspace-name" |
55-
| contentStreamId | "cs-imported-identifier" |
56-
| nodeAggregateId | "acme-site-sites" |
57-
| nodeTypeName | "Neos.Neos:Sites" |
56+
| Key | Expected |
57+
| workspaceName | "live" |
58+
| contentStreamId | "cs-identifier" |
59+
| nodeAggregateId | "acme-site-sites" |
60+
| nodeTypeName | "Neos.Neos:Sites" |
5861
And event at index 2 is of type "NodeAggregateWithNodeWasCreated" with payload:
59-
| Key | Expected |
60-
| workspaceName | "workspace-name" |
61-
| contentStreamId | "cs-imported-identifier" |
62-
| nodeAggregateId | "acme-site" |
63-
| nodeTypeName | "Vendor.Site:HomePage" |
62+
| Key | Expected |
63+
| workspaceName | "live" |
64+
| contentStreamId | "cs-identifier" |
65+
| nodeAggregateId | "acme-site" |
66+
| nodeTypeName | "Vendor.Site:HomePage" |
6467

6568
Scenario: Import faulty event stream with explicit "ContentStreamWasCreated" does not duplicate content-stream
6669
see issue https://github.com/neos/neos-development-collection/issues/4298
@@ -73,7 +76,7 @@ Feature: As a user of the CR I want to export the event stream
7376
"""
7477
And I import the events.jsonl
7578

76-
And I expect a MigrationError with the message
79+
And I expect a migration exception with the message
7780
"""
7881
Failed to read events. ContentStreamWasCreated is not expected in imported event stream.
7982
"""

Neos.ContentRepository.Export/src/Asset/AssetExporter.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
2+
23
declare(strict_types=1);
4+
35
namespace Neos\ContentRepository\Export\Asset;
46

57
use League\Flysystem\Filesystem;
@@ -14,7 +16,8 @@ public function __construct(
1416
private readonly Filesystem $files,
1517
private readonly AssetLoaderInterface $assetLoader,
1618
private readonly ResourceLoaderInterface $resourceLoader,
17-
) {}
19+
) {
20+
}
1821

1922
public function exportAsset(string $assetId): void
2023
{

Neos.ContentRepository.Export/src/Event/ValueObject/ExportedEvent.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@ public function __construct(
2424

2525
public static function fromRawEvent(Event $event): self
2626
{
27+
$payload = \json_decode($event->data->value, true, 512, JSON_THROW_ON_ERROR);
28+
// unset content stream id as this is overwritten during import
29+
unset($payload['contentStreamId'], $payload['workspaceName']);
2730
return new self(
2831
$event->id->value,
2932
$event->type->value,
30-
\json_decode($event->data->value, true, 512, JSON_THROW_ON_ERROR),
33+
$payload,
3134
$event->metadata?->value ?? [],
3235
);
3336
}

Neos.ContentRepository.Export/src/ExportService.php

Lines changed: 0 additions & 63 deletions
This file was deleted.

Neos.ContentRepository.Export/src/ExportServiceFactory.php

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Neos\ContentRepository\Export\Factory;
6+
7+
use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies;
8+
use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface;
9+
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
10+
use Neos\ContentRepository\Export\Processors\EventExportProcessor;
11+
12+
/**
13+
* @implements ContentRepositoryServiceFactoryInterface<EventExportProcessor>
14+
*/
15+
final readonly class EventExportProcessorFactory implements ContentRepositoryServiceFactoryInterface
16+
{
17+
public function __construct(
18+
private ContentStreamId $contentStreamId,
19+
) {
20+
}
21+
22+
public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): EventExportProcessor
23+
{
24+
return new EventExportProcessor(
25+
$this->contentStreamId,
26+
$serviceFactoryDependencies->eventStore,
27+
);
28+
}
29+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Neos\ContentRepository\Export\Factory;
6+
7+
use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies;
8+
use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface;
9+
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
10+
use Neos\ContentRepository\Export\Processors\EventStoreImportProcessor;
11+
12+
/**
13+
* @implements ContentRepositoryServiceFactoryInterface<EventStoreImportProcessor>
14+
*/
15+
final readonly class EventStoreImportProcessorFactory implements ContentRepositoryServiceFactoryInterface
16+
{
17+
public function __construct(
18+
private WorkspaceName $targetWorkspaceName,
19+
private bool $keepEventIds,
20+
) {
21+
}
22+
23+
public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): EventStoreImportProcessor
24+
{
25+
return new EventStoreImportProcessor(
26+
$this->targetWorkspaceName,
27+
$this->keepEventIds,
28+
$serviceFactoryDependencies->eventStore,
29+
$serviceFactoryDependencies->eventNormalizer,
30+
$serviceFactoryDependencies->contentRepository,
31+
);
32+
}
33+
}

0 commit comments

Comments
 (0)