Skip to content

Commit

Permalink
Merge pull request #4659 from neos/feature/4550-subtree-tags
Browse files Browse the repository at this point in the history
!!! FEATURE: Subtree Tags
  • Loading branch information
bwaidelich authored Mar 15, 2024
2 parents a439b31 + fbe521a commit bfd43df
Show file tree
Hide file tree
Showing 85 changed files with 2,891 additions and 1,747 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalContentGraphProjectionFactory;
use Neos\ContentGraph\DoctrineDbalAdapter\DoctrineDbalProjectionIntegrityViolationDetectionRunnerFactory;
use Neos\ContentGraph\DoctrineDbalAdapter\Domain\Repository\NodeFactory;
use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint;
use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTag;
use Neos\ContentRepository\Core\Feature\SubtreeTagging\Dto\SubtreeTags;
use Neos\ContentRepository\Core\Projection\ContentGraph\NodeTags;
use Neos\ContentRepository\Core\SharedModel\Id\UuidFactory;
use Neos\ContentRepository\Core\SharedModel\Workspace\ContentStreamId;
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId;
Expand Down Expand Up @@ -64,53 +68,24 @@ public function setupDbalGraphAdapterIntegrityViolationTrait()
}

/**
* @When /^I remove the following restriction relation:$/
* @When /^I remove the following subtree tag:$/
* @param TableNode $payloadTable
* @throws DBALException
* @throws InvalidArgumentException
*/
public function iRemoveTheFollowingRestrictionRelation(TableNode $payloadTable): void
public function iRemoveTheFollowingSubtreeTag(TableNode $payloadTable): void
{
$dataset = $this->transformPayloadTableToDataset($payloadTable);
$record = $this->transformDatasetToRestrictionRelationRecord($dataset);

$this->dbalClient->getConnection()->delete(
$this->getTableNamePrefix() . '_restrictionrelation',
$record
);
}

/**
* @When /^I detach the following restriction relation from its origin:$/
* @param TableNode $payloadTable
* @throws DBALException
*/
public function iDetachTheFollowingRestrictionRelationFromItsOrigin(TableNode $payloadTable): void
{
$dataset = $this->transformPayloadTableToDataset($payloadTable);
$record = $this->transformDatasetToRestrictionRelationRecord($dataset);
$this->dbalClient->getConnection()->update(
$this->getTableNamePrefix() . '_restrictionrelation',
[
'originnodeaggregateid' => (string)TestingNodeAggregateId::nonExistent()
],
$record
);
}

/**
* @When /^I detach the following restriction relation from its target:$/
* @param TableNode $payloadTable
* @throws DBALException
*/
public function iDetachTheFollowingRestrictionRelationFromItsTarget(TableNode $payloadTable): void
{
$dataset = $this->transformPayloadTableToDataset($payloadTable);
$record = $this->transformDatasetToRestrictionRelationRecord($dataset);
$subtreeTagToRemove = SubtreeTag::fromString($dataset['subtreeTag']);
$record = $this->transformDatasetToHierarchyRelationRecord($dataset);
$subtreeTags = NodeFactory::extractNodeTagsFromJson($record['subtreetags']);
if (!$subtreeTags->contain($subtreeTagToRemove)) {
throw new \RuntimeException(sprintf('Failed to remove subtree tag "%s" because that tag is not set', $subtreeTagToRemove->value), 1708618267);
}
$this->dbalClient->getConnection()->update(
$this->getTableNamePrefix() . '_restrictionrelation',
$this->getTableNamePrefix() . '_hierarchyrelation',
[
'affectednodeaggregateid' => (string)TestingNodeAggregateId::nonExistent()
'subtreetags' => json_encode($subtreeTags->without($subtreeTagToRemove), JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT),
],
$record
);
Expand Down Expand Up @@ -234,87 +209,83 @@ private function transformDatasetToReferenceRelationRecord(array $dataset): arra
{
return [
'name' => $dataset['referenceName'],
'nodeanchorpoint' => $this->findRelationAnchorPointByIds(
'nodeanchorpoint' => $this->findHierarchyRelationByIds(
ContentStreamId::fromString($dataset['contentStreamId']),
DimensionSpacePoint::fromArray($dataset['dimensionSpacePoint']),
NodeAggregateId::fromString($dataset['sourceNodeAggregateId'])
),
)['childnodeanchor'],
'destinationnodeaggregateid' => $dataset['destinationNodeAggregateId']
];
}

private function transformDatasetToRestrictionRelationRecord(array $dataset): array
{
$dimensionSpacePoint = DimensionSpacePoint::fromArray($dataset['dimensionSpacePoint']);

return [
'contentstreamid' => $dataset['contentStreamId'],
'dimensionspacepointhash' => $dimensionSpacePoint->hash,
'originnodeaggregateid' => $dataset['originNodeAggregateId'],
'affectednodeaggregateid' => $dataset['affectedNodeAggregateId'],
];
}

private function transformDatasetToHierarchyRelationRecord(array $dataset): array
{
$dimensionSpacePoint = DimensionSpacePoint::fromArray($dataset['dimensionSpacePoint']);
$parentNodeAggregateId = TestingNodeAggregateId::fromString($dataset['parentNodeAggregateId']);
$childAggregateId = TestingNodeAggregateId::fromString($dataset['childNodeAggregateId']);

$parentHierarchyRelation = $parentNodeAggregateId->isNonExistent()
? null
: $this->findHierarchyRelationByIds(
ContentStreamId::fromString($dataset['contentStreamId']),
$dimensionSpacePoint,
NodeAggregateId::fromString($dataset['parentNodeAggregateId'])
);

$childHierarchyRelation = $childAggregateId->isNonExistent()
? null
: $this->findHierarchyRelationByIds(
ContentStreamId::fromString($dataset['contentStreamId']),
$dimensionSpacePoint,
NodeAggregateId::fromString($dataset['childNodeAggregateId'])
);

return [
'contentstreamid' => $dataset['contentStreamId'],
'dimensionspacepoint' => $dimensionSpacePoint->toJson(),
'dimensionspacepointhash' => $dimensionSpacePoint->hash,
'parentnodeanchor' => $parentNodeAggregateId->isNonExistent()
? 9999999
: $this->findRelationAnchorPointByIds(
ContentStreamId::fromString($dataset['contentStreamId']),
$dimensionSpacePoint,
NodeAggregateId::fromString($dataset['parentNodeAggregateId'])
),
'childnodeanchor' => $childAggregateId->isNonExistent()
? 8888888
: $this->findRelationAnchorPointByIds(
ContentStreamId::fromString($dataset['contentStreamId']),
$dimensionSpacePoint,
NodeAggregateId::fromString($dataset['childNodeAggregateId'])
),
'position' => $dataset['position'] ?? 0
'parentnodeanchor' => $parentHierarchyRelation !== null ? $parentHierarchyRelation['childnodeanchor'] : 9999999,
'childnodeanchor' => $childHierarchyRelation !== null ? $childHierarchyRelation['childnodeanchor'] : 8888888,
'position' => $dataset['position'] ?? $parentHierarchyRelation !== null ? $parentHierarchyRelation['position'] : 0,
'subtreetags' => $parentHierarchyRelation !== null ? $parentHierarchyRelation['subtreetags'] : '{}',
];
}

private function findRelationAnchorPointByDataset(array $dataset): int
{
$dimensionSpacePoint = DimensionSpacePoint::fromArray($dataset['originDimensionSpacePoint'] ?? $dataset['dimensionSpacePoint']);

return $this->findRelationAnchorPointByIds(
return $this->findHierarchyRelationByIds(
ContentStreamId::fromString($dataset['contentStreamId']),
$dimensionSpacePoint,
NodeAggregateId::fromString($dataset['nodeAggregateId'] ?? $dataset['childNodeAggregateId'])
);
)['childnodeanchor'];
}

private function findRelationAnchorPointByIds(
private function findHierarchyRelationByIds(
ContentStreamId $contentStreamId,
DimensionSpacePoint $dimensionSpacePoint,
NodeAggregateId $nodeAggregateId
): int {
): array {
$nodeRecord = $this->dbalClient->getConnection()->executeQuery(
'SELECT n.relationanchorpoint
FROM ' . $this->getTableNamePrefix() . '_node n
INNER JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation h
ON n.relationanchorpoint = h.childnodeanchor
WHERE n.nodeaggregateid = :nodeAggregateId
AND h.contentstreamid = :contentStreamId
AND h.dimensionspacepointhash = :dimensionSpacePointHash',
'SELECT h.*
FROM ' . $this->getTableNamePrefix() . '_node n
INNER JOIN ' . $this->getTableNamePrefix() . '_hierarchyrelation h
ON n.relationanchorpoint = h.childnodeanchor
WHERE n.nodeaggregateid = :nodeAggregateId
AND h.contentstreamid = :contentStreamId
AND h.dimensionspacepointhash = :dimensionSpacePointHash',
[
'contentStreamId' => $contentStreamId->value,
'dimensionSpacePointHash' => $dimensionSpacePoint->hash,
'nodeAggregateId' => $nodeAggregateId->value
]
)->fetchAssociative();
if ($nodeRecord === false) {
throw new \InvalidArgumentException(sprintf('Failed to find hierarchy relation for content stream "%s", dimension space point "%s" and node aggregate id "%s"', $contentStreamId->value, $dimensionSpacePoint->hash, $nodeAggregateId->value), 1708617712);
}

return $nodeRecord['relationanchorpoint'];
return $nodeRecord;
}

private function transformPayloadTableToDataset(TableNode $payloadTable): array
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@contentrepository
Feature: Run integrity violation detection regarding restriction relations
Feature: Run integrity violation detection regarding subtree tag inheritance

As a user of the CR I want to know whether there are nodes with restriction relations missing from their ancestors
As a user of the CR I want to know whether there are nodes with subtree tags that are not inherited from its ancestors

Background:
Given using the following content dimensions:
Expand Down Expand Up @@ -58,18 +58,20 @@ Feature: Run integrity violation detection regarding restriction relations
| parentNodeAggregateId | "sir-nodeward-nodington-iii" |
| nodeName | "child-document" |
| nodeAggregateClassification | "regular" |
And the event NodeAggregateWasDisabled was published with payload:
And the event SubtreeWasTagged was published with payload:
| Key | Value |
| contentStreamId | "cs-identifier" |
| nodeAggregateId | "sir-david-nodenborough" |
| affectedDimensionSpacePoints | [{"language":"de"},{"language":"gsw"},{"language":"fr"}] |
| tag | "disabled" |
And the graph projection is fully up to date
And I remove the following restriction relation:
| Key | Value |
| contentStreamId | "cs-identifier" |
| dimensionSpacePoint | {"language":"de"} |
| originNodeAggregateId | "sir-david-nodenborough" |
| affectedNodeAggregateId | "nody-mc-nodeface" |
And I remove the following subtree tag:
| Key | Value |
| contentStreamId | "cs-identifier" |
| dimensionSpacePoint | {"language":"de"} |
| parentNodeAggregateId | "sir-nodeward-nodington-iii" |
| childNodeAggregateId | "nody-mc-nodeface" |
| subtreeTag | "disabled" |
And I run integrity violation detection
Then I expect the integrity violation detection result to contain exactly 1 error
And I expect integrity violation detection result error number 1 to have code 1597837797
Loading

0 comments on commit bfd43df

Please sign in to comment.