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: AssetUsage as CatchUpHook #5258

Merged
merged 12 commits into from
Oct 1, 2024
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ jobs:
touch ${{ github.workspace }}/Data/DebugDatabaseDumps/keep

./flow package:list --loading-order
FLOW_CONTEXT=Testing/Behat ./flow doctrine:migrate --quiet
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(how) is that related?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behat tests are not working without this change, as the CatchUpHook expects the doctrine:migrate command to be executed. But it's not when we run the Neos.ContentRepository.BehavioralTests.

This was not an issue before, as these tests where only relying on the projection tables. But the new CatchUpHook now, gets executed also in these test cases.

https://github.com/neos/neos-development-collection/blob/9.0/.composer.json#L34-L41

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is our long standing: you have to do doctrine migrate sometimes problem otherwise it will not work problem that youll fix with the catchup thing as far as i know. see slack: https://neos-project.slack.com/archives/C04PYL8H3/p1727209924914109?thread_ts=1727117857.409749&cid=C04PYL8H3

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upps i was mistaken, this is just really similar to this problem: #5005

In #4878 i also discovered that disabling the catchup hooks will speed up the tests ... and as they are just run along without any assertions in the core tests we should consider doing this some point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, true. But we need to be able to enable it for the test cases where we test the catchup hook 🤷‍♂️

cd Packages/Neos

# composer test:behavioral
Expand Down
9 changes: 6 additions & 3 deletions Neos.ContentRepository.Export/src/ExportService.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
use League\Flysystem\Filesystem;
use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface;
use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder;
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
use Neos\ContentRepository\Export\Processors\AssetExportProcessor;
use Neos\ContentRepository\Export\Processors\EventExportProcessor;
use Neos\EventStore\EventStoreInterface;
use Neos\Media\Domain\Repository\AssetRepository;
use Neos\Neos\AssetUsage\Projection\AssetUsageFinder;
use Neos\Neos\AssetUsage\AssetUsageService;

/**
* @internal
Expand All @@ -19,10 +20,11 @@ class ExportService implements ContentRepositoryServiceInterface
{

public function __construct(
private readonly ContentRepositoryId $contentRepositoryId,
private readonly Filesystem $filesystem,
private readonly WorkspaceFinder $workspaceFinder,
private readonly AssetRepository $assetRepository,
private readonly AssetUsageFinder $assetUsageFinder,
private readonly AssetUsageService $assetUsageService,
private readonly EventStoreInterface $eventStore,
) {
}
Expand All @@ -37,10 +39,11 @@ public function runAllProcessors(\Closure $outputLineFn, bool $verbose = false):
$this->eventStore
),
'Exporting assets' => new AssetExportProcessor(
$this->contentRepositoryId,
$this->filesystem,
$this->assetRepository,
$this->workspaceFinder,
$this->assetUsageFinder
$this->assetUsageService
)
];

Expand Down
7 changes: 4 additions & 3 deletions Neos.ContentRepository.Export/src/ExportServiceFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface;
use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder;
use Neos\Media\Domain\Repository\AssetRepository;
use Neos\Neos\AssetUsage\Projection\AssetUsageFinder;
use Neos\Neos\AssetUsage\AssetUsageService;

/**
* @internal
Expand All @@ -21,17 +21,18 @@ public function __construct(
private readonly Filesystem $filesystem,
private readonly WorkspaceFinder $workspaceFinder,
private readonly AssetRepository $assetRepository,
private readonly AssetUsageFinder $assetUsageFinder,
private readonly AssetUsageService $assetUsageService,
) {
}

public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ExportService
{
return new ExportService(
$serviceFactoryDependencies->contentRepositoryId,
$this->filesystem,
$this->workspaceFinder,
$this->assetRepository,
$this->assetUsageFinder,
$this->assetUsageService,
$serviceFactoryDependencies->eventStore,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use League\Flysystem\Filesystem;
use Neos\ContentRepository\Core\Projection\Workspace\WorkspaceFinder;
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\ContentRepository\Export\Asset\ValueObject\SerializedAsset;
use Neos\ContentRepository\Export\Asset\ValueObject\SerializedImageVariant;
Expand All @@ -15,8 +16,8 @@
use Neos\Media\Domain\Model\AssetVariantInterface;
use Neos\Media\Domain\Model\ImageVariant;
use Neos\Media\Domain\Repository\AssetRepository;
use Neos\Neos\AssetUsage\AssetUsageService;
use Neos\Neos\AssetUsage\Dto\AssetUsageFilter;
use Neos\Neos\AssetUsage\Projection\AssetUsageFinder;

/**
* Processor that exports all assets and resources used in the Neos live workspace to the file system
Expand All @@ -29,10 +30,11 @@ final class AssetExportProcessor implements ProcessorInterface
private array $callbacks = [];

public function __construct(
private readonly ContentRepositoryId $contentRepositoryId,
private readonly Filesystem $files,
private readonly AssetRepository $assetRepository,
private readonly WorkspaceFinder $workspaceFinder,
private readonly AssetUsageFinder $assetUsageFinder,
private readonly AssetUsageService $assetUsageService,
) {}

public function onMessage(\Closure $callback): void
Expand All @@ -47,13 +49,13 @@ public function run(): ProcessorResult
if ($liveWorkspace === null) {
return ProcessorResult::error('Failed to find live workspace');
}
$assetFilter = AssetUsageFilter::create()->withContentStream($liveWorkspace->currentContentStreamId)->groupByAsset();
$assetFilter = AssetUsageFilter::create()->withWorkspaceName($liveWorkspace->workspaceName)->groupByAsset();

$numberOfExportedAssets = 0;
$numberOfExportedImageVariants = 0;
$numberOfErrors = 0;

foreach ($this->assetUsageFinder->findByFilter($assetFilter) as $assetUsage) {
foreach ($this->assetUsageService->findByFilter($this->contentRepositoryId, $assetFilter) as $assetUsage) {
/** @var Asset|null $asset */
$asset = $this->assetRepository->findByIdentifier($assetUsage->assetId);
if ($asset === null) {
Expand Down
2 changes: 1 addition & 1 deletion Neos.Media.Browser/Classes/Controller/UsageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public function relatedNodesAction(AssetInterface $asset)

$contentRepository = $this->contentRepositoryRegistry->get($usage->getContentRepositoryId());

$workspace = $contentRepository->getWorkspaceFinder()->findOneByCurrentContentStreamId($usage->getContentStreamId());
$workspace = $contentRepository->getWorkspaceFinder()->findOneByName($usage->getWorkspaceName());

// FIXME: AssetUsageReference->workspaceName ?
$nodeAggregate = $contentRepository->getContentGraph($workspace->workspaceName)->findNodeAggregateById(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,21 @@
<f:switch expression="{true}">
<f:case value="{inaccessibleRelation.workspace.personalWorkspace}">
<i class="fas fa-user"
title="{neos:backend.translate(id: 'workspaces.personalWorkspace', source: 'Modules', package: 'Neos.Neos')}"
title="{neos:backend.translate(id: 'workspaces.personalWorkspace', source: 'Main', package: 'Neos.Workspace.Ui')}"
data-neos-toggle="tooltip"></i>
{neos:backend.translate(id: 'workspaces.personalWorkspace', source: 'Modules', package: 'Neos.Neos')}
{neos:backend.translate(id: 'workspaces.personalWorkspace', source: 'Main', package: 'Neos.Workspace.Ui')}
</f:case>
<f:case value="{inaccessibleRelation.workspace.privateWorkspace}">
<i class="fas fa-shield"
title="{neos:backend.translate(id: 'workspaces.privateWorkspace', source: 'Modules', package: 'Neos.Neos')}"
title="{neos:backend.translate(id: 'workspaces.privateWorkspace', source: 'Main', package: 'Neos.Workspace.Ui')}"
data-neos-toggle="tooltip"></i>
{neos:backend.translate(id: 'workspaces.privateWorkspace', source: 'Modules', package:
'Neos.Neos')}
{neos:backend.translate(id: 'workspaces.privateWorkspace', source: 'Main', package: 'Neos.Workspace.Ui')}
</f:case>
<f:case value="{inaccessibleRelation.workspace.internalWorkspace}">
<i class="fas fa-group"
title="{neos:backend.translate(id: 'workspaces.internalWorkspace', source: 'Modules', package: 'Neos.Neos')}"
title="{neos:backend.translate(id: 'workspaces.internalWorkspace', source: 'Main', package: 'Neos.Workspace.Ui')}"
data-neos-toggle="tooltip"></i>
{neos:backend.translate(id: 'workspaces.internalWorkspace', source: 'Modules', package:
'Neos.Neos')}
{neos:backend.translate(id: 'workspaces.internalWorkspace', source: 'Main', package: 'Neos.Workspace.Ui')}
</f:case>
<f:defaultCase>
---
Expand Down Expand Up @@ -96,8 +94,8 @@
<f:if condition="{nodeInformation.documentNode}">
<f:then>
<neos:link.node node="{nodeInformation.documentNode}" target="_blank"
title="{neos:backend.translate(id: 'workspaces.openPageInWorkspace', source: 'Modules', package: 'Neos.Neos', arguments: {0: nodeInformation.workspace.workspaceTitle.value})}">
<span title="{f:render(partial: 'Module/Shared/DocumentBreadcrumb', arguments: {node: nodeInformation.documentNode})}" data-neos-toggle="tooltip">{nodeInformation.documentNode.label}</span>
title="{neos:backend.translate(id: 'workspaces.openPageInWorkspace', source: 'Main', package: 'Neos.Workspace.Ui', arguments: {0: nodeInformation.workspace.workspaceTitle.value})}">
<span title="{f:render(partial: 'Module/Shared/DocumentBreadcrumb', arguments: {node: nodeInformation.documentNode})}" data-neos-toggle="tooltip">{neos:node.label(node: nodeInformation.documentNode)}</span>
<i class="fas fa-external-link-alt"></i>
</neos:link.node>
</f:then>
Expand All @@ -113,7 +111,7 @@
title="{f:if(condition: nodeInformation.node.nodeType.label, then: '{neos:backend.translate(id: nodeInformation.node.nodeType.label, package: \'Neos.Neos\')}', else: '{nodeInformation.node.nodeType.name}')}"
data-neos-toggle="tooltip" data-placement="left"></i>
</f:if>
<span title="{nodeInformation.node.path}" data-neos-toggle="tooltip" data-placement="left">{nodeInformation.node.label}</span>
<span title="{nodeInformation.node.path}" data-neos-toggle="tooltip" data-placement="left">{neos:node.label(node: nodeInformation.node)}</span>
</td>
<td>
<f:if condition="{contentDimensions}">
Expand All @@ -124,19 +122,19 @@
<f:if condition="{userWorkspace} == {nodeInformation.workspace}">
<f:then>
<i class="fas fa-user"
title="{neos:backend.translate(id: 'workspaces.personalWorkspace', source: 'Modules', package: 'Neos.Neos')}"
title="{neos:backend.translate(id: 'workspaces.personalWorkspace', source: 'Main', package: 'Neos.Workspace.Ui')}"
data-neos-toggle="tooltip"></i>
</f:then>
<f:else>
<f:if condition="{nodeInformation.workspace.privateWorkspace}">
<f:then>
<i class="fas fa-shield"
title="{neos:backend.translate(id: 'workspaces.privateWorkspace', source: 'Modules', package: 'Neos.Neos')}"
title="{neos:backend.translate(id: 'workspaces.privateWorkspace', source: 'Main', package: 'Neos.Workspace.Ui')}"
data-neos-toggle="tooltip"></i>
</f:then>
<f:else>
<i class="fas fa-group"
title="{neos:backend.translate(id: 'workspaces.internalWorkspace', source: 'Modules', package: 'Neos.Neos')}"
title="{neos:backend.translate(id: 'workspaces.internalWorkspace', source: 'Main', package: 'Neos.Workspace.Ui')}"
data-neos-toggle="tooltip"></i>
</f:else>
</f:if>
Expand Down
88 changes: 88 additions & 0 deletions Neos.Neos/Classes/AssetUsage/AssetUsageIndexingProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

declare(strict_types=1);

namespace Neos\Neos\AssetUsage;

use Neos\ContentRepository\Core\ContentRepository;
use Neos\ContentRepository\Core\NodeType\NodeTypeName;
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindChildNodesFilter;
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
use Neos\ContentRepository\Core\Projection\ContentGraph\VisibilityConstraints;
use Neos\ContentRepository\Core\SharedModel\Exception\NodeAggregateCurrentlyDoesNotExist;
use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\Neos\AssetUsage\Service\AssetUsageIndexingService;

readonly class AssetUsageIndexingProcessor
{
public function __construct(
private AssetUsageIndexingService $assetUsageIndexingService
) {
}

/**
* @param callable(string $message):void|null $callback
*/
public function buildIndex(ContentRepository $contentRepository, NodeTypeName $nodeTypeName, callable $callback = null): void
{
$variationGraph = $contentRepository->getVariationGraph();

$workspaceFinder = $contentRepository->getWorkspaceFinder();
$liveWorkspace = $workspaceFinder->findOneByName(WorkspaceName::forLive());
if ($liveWorkspace === null) {
throw WorkspaceDoesNotExist::butWasSupposedTo(WorkspaceName::forLive());
}

$this->assetUsageIndexingService->pruneIndex($contentRepository->id);

$workspaces = [$liveWorkspace];

$this->dispatchMessage($callback, sprintf('ContentRepository "%s"', $contentRepository->id->value));
while ($workspaces !== []) {
$workspace = array_shift($workspaces);

$contentGraph = $contentRepository->getContentGraph($workspace->workspaceName);
$this->dispatchMessage($callback, sprintf(' Workspace: %s', $contentGraph->getWorkspaceName()->value));

$dimensionSpacePoints = $variationGraph->getDimensionSpacePoints();

$rootNodeAggregate = $contentGraph->findRootNodeAggregateByType(
$nodeTypeName
);
if ($rootNodeAggregate === null) {
$this->dispatchMessage($callback, sprintf(' ERROR: %s', "Root node aggregate was not found."));
continue;
}
$rootNodeAggregateId = $rootNodeAggregate->nodeAggregateId;

foreach ($dimensionSpacePoints as $dimensionSpacePoint) {
$this->dispatchMessage($callback, sprintf(' DimensionSpacePoint: %s', $dimensionSpacePoint->toJson()));

$subgraph = $contentGraph->getSubgraph($dimensionSpacePoint, VisibilityConstraints::withoutRestrictions());
$childNodes = iterator_to_array($subgraph->findChildNodes($rootNodeAggregateId, FindChildNodesFilter::create()));

while ($childNodes !== []) {
/** @var Node $childNode */
$childNode = array_shift($childNodes);
if (!$childNode->originDimensionSpacePoint->equals($childNode->dimensionSpacePoint)) {
continue;
}
$this->assetUsageIndexingService->updateIndex($contentRepository->id, $childNode);
array_push($childNodes, ...iterator_to_array($subgraph->findChildNodes($childNode->aggregateId, FindChildNodesFilter::create())));
}
}

array_push($workspaces, ...array_values($workspaceFinder->findByBaseWorkspace($workspace->workspaceName)));
}
}

private function dispatchMessage(?callable $callback, string $value): void
{
if ($callback === null) {
return;
}

$callback($value);
}
}
28 changes: 28 additions & 0 deletions Neos.Neos/Classes/AssetUsage/AssetUsageService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Neos\Neos\AssetUsage;

use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
use Neos\Flow\Annotations as Flow;
use Neos\Neos\AssetUsage\Domain\AssetUsageRepository;
use Neos\Neos\AssetUsage\Dto\AssetUsageFilter;
use Neos\Neos\AssetUsage\Dto\AssetUsages;

/**
* Central authority to look up or remove asset usages in specific a ContentRepository
*/
#[Flow\Scope('singleton')]
class AssetUsageService
{
public function __construct(
private readonly AssetUsageRepository $assetUsageRepository,
) {
}

public function findByFilter(ContentRepositoryId $contentRepositoryId, AssetUsageFilter $filter): AssetUsages
{
return $this->assetUsageRepository->findUsages($contentRepositoryId, $filter);
}
}
2 changes: 1 addition & 1 deletion Neos.Neos/Classes/AssetUsage/AssetUsageStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function getUsageReferences(AssetInterface $asset): array
$convertedUsages[] = new AssetUsageReference(
$asset,
ContentRepositoryId::fromString($contentRepositoryId),
$usage->contentStreamId,
$usage->workspaceName,
$usage->originDimensionSpacePoint,
$usage->nodeAggregateId
);
Expand Down
Loading
Loading