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

IBX-704: Added aggregation support to NodeFactory #1792

Merged
1 change: 1 addition & 0 deletions src/bundle/Resources/config/default_parameters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,4 @@ parameters:

ez_systems.multifile_upload.max_file_size: 5242880

ibexa.admin_ui.content_tree.node_factory.max_location_ids_in_single_aggregation: 100
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ services:
$contentService: '@ezpublish.api.service.content'
$translationHelper: '@ezpublish.translation_helper'
$configResolver: '@ezpublish.config.resolver'
$maxLocationIdsInSingleAggregation: '%ibexa.admin_ui.content_tree.node_factory.max_location_ids_in_single_aggregation%'

EzSystems\EzPlatformAdminUi\UI\Config\Provider\Module\ContentTree:
tags:
Expand Down
129 changes: 100 additions & 29 deletions src/lib/UI/Module/ContentTree/NodeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use eZ\Publish\API\Repository\Values\Content\Query;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
use eZ\Publish\API\Repository\Values\Content\Query\SortClause;
use eZ\Publish\API\Repository\Values\Content\Search\AggregationResult\TermAggregationResult;
use eZ\Publish\API\Repository\Values\Content\Search\SearchResult;
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
use eZ\Publish\Core\Helper\TranslationHelper;
Expand Down Expand Up @@ -46,16 +47,21 @@ final class NodeFactory
/** @var \eZ\Publish\Core\MVC\ConfigResolverInterface */
private $configResolver;

/** @var int */
private $maxLocationIdsInSingleAggregation;

public function __construct(
ContentService $contentService,
SearchService $searchService,
TranslationHelper $translationHelper,
ConfigResolverInterface $configResolver
ConfigResolverInterface $configResolver,
int $maxLocationIdsInSingleAggregation
) {
$this->contentService = $contentService;
$this->searchService = $searchService;
$this->translationHelper = $translationHelper;
$this->configResolver = $configResolver;
$this->maxLocationIdsInSingleAggregation = $maxLocationIdsInSingleAggregation;
}

/**
Expand All @@ -72,18 +78,21 @@ public function createNode(
string $sortOrder = Query::SORT_ASC
): Node {
$uninitializedContentInfoList = [];
$node = $this->buildNode($location, $uninitializedContentInfoList, $loadSubtreeRequestNode, $loadChildren, $depth, $sortClause, $sortOrder);
$containerLocations = [];
$node = $this->buildNode($location, $uninitializedContentInfoList, $containerLocations, $loadSubtreeRequestNode, $loadChildren, $depth, $sortClause, $sortOrder);
$contentById = $this->contentService->loadContentListByContentInfo($uninitializedContentInfoList);

$aggregatedChildrenCount = null;
if ($this->searchService->supports(SearchService::CAPABILITY_AGGREGATIONS)) {
$aggregatedChildrenCount = $this->countAggregatedSubitems($containerLocations);
}

$this->supplyTranslatedContentName($node, $contentById);
$this->supplyChildrenCount($node, $aggregatedChildrenCount);

return $node;
}

/**
* @param \EzSystems\EzPlatformAdminUi\REST\Value\ContentTree\LoadSubtreeRequestNode|null $loadSubtreeRequestNode
*
* @return int
*/
private function resolveLoadLimit(?LoadSubtreeRequestNode $loadSubtreeRequestNode): int
{
$limit = $this->getSetting('load_more_limit');
Expand All @@ -106,7 +115,7 @@ private function findSubitems(
?string $sortClause = null,
string $sortOrder = Query::SORT_ASC
): SearchResult {
$searchQuery = $this->getSearchQuery($parentLocation);
$searchQuery = $this->getSearchQuery($parentLocation->id);

$searchQuery->limit = $limit;
$searchQuery->offset = $offset;
Expand All @@ -117,13 +126,11 @@ private function findSubitems(

/**
* @param \eZ\Publish\API\Repository\Values\Content\Location $parentLocation
*
* @return \eZ\Publish\API\Repository\Values\Content\LocationQuery
*/
private function getSearchQuery(Location $parentLocation): LocationQuery
private function getSearchQuery(int $parentLocationId): LocationQuery
{
$searchQuery = new LocationQuery();
$searchQuery->filter = new Criterion\ParentLocationId($parentLocation->id);
$searchQuery->filter = new Criterion\ParentLocationId($parentLocationId);

$contentTypeCriterion = null;

Expand All @@ -144,12 +151,6 @@ private function getSearchQuery(Location $parentLocation): LocationQuery
return $searchQuery;
}

/**
* @param int $locationId
* @param \EzSystems\EzPlatformAdminUi\REST\Value\ContentTree\LoadSubtreeRequestNode $loadSubtreeRequestNode
*
* @return \EzSystems\EzPlatformAdminUi\REST\Value\ContentTree\LoadSubtreeRequestNode|null
*/
private function findChild(int $locationId, LoadSubtreeRequestNode $loadSubtreeRequestNode): ?LoadSubtreeRequestNode
{
foreach ($loadSubtreeRequestNode->children as $child) {
Expand All @@ -162,15 +163,11 @@ private function findChild(int $locationId, LoadSubtreeRequestNode $loadSubtreeR
}

/**
* @param \eZ\Publish\API\Repository\Values\Content\Location $parentLocation
*
* @return int
*
* @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
*/
private function countSubitems(Location $parentLocation): int
private function countSubitems(int $parentLocationId): int
{
$searchQuery = $this->getSearchQuery($parentLocation);
$searchQuery = $this->getSearchQuery($parentLocationId);

$searchQuery->limit = 0;
$searchQuery->offset = 0;
Expand All @@ -179,6 +176,58 @@ private function countSubitems(Location $parentLocation): int
return $this->searchService->findLocations($searchQuery)->totalCount;
}

/**
* @param \eZ\Publish\API\Repository\Values\Content\Location[] $containerLocations
*/
private function countAggregatedSubitems(array $containerLocations): array
{
if (empty($containerLocations)) {
return [];
}

if (\count($containerLocations) > $this->maxLocationIdsInSingleAggregation) {
$containerLocationsChunks = array_chunk($containerLocations, $this->maxLocationIdsInSingleAggregation);

$result = [];
foreach ($containerLocationsChunks as $containerLocationsChunk) {
$result = array_replace($result, $this->countAggregatedSubitems($containerLocationsChunk));
}

return $result;
}

$parentLocationIds = array_column($containerLocations, 'id');

$searchQuery = new LocationQuery();
$searchQuery->filter = new Criterion\ParentLocationId($parentLocationIds);
$locationChildrenTermAggregation = new Query\Aggregation\Location\LocationChildrenTermAggregation('childrens');
$locationChildrenTermAggregation->setLimit(\count($parentLocationIds));
$searchQuery->aggregations[] = $locationChildrenTermAggregation;

$result = $this->searchService->findLocations($searchQuery);

if ($result->aggregations->has('childrens')) {
return $this->aggregationResultToArray($result->aggregations->get('childrens'));
}

return [];
}

/**
* @return array<int,int>
*/
private function aggregationResultToArray(TermAggregationResult $aggregationResult): array
mateuszbieniek marked this conversation as resolved.
Show resolved Hide resolved
{
$resultsAsArray = [];
foreach ($aggregationResult->getEntries() as $entry) {
/** @var \eZ\Publish\API\Repository\Values\Content\Location $location */
$location = $entry->getKey();
$resultsAsArray[$location->id] = $entry->getCount();
}

return $resultsAsArray;
}

private function getSetting(string $name)
{
return $this->configResolver->getParameter("content_tree_module.$name");
Expand Down Expand Up @@ -233,6 +282,7 @@ private function getSortClauses(
private function buildNode(
Location $location,
array &$uninitializedContentInfoList,
array &$containerLocations,
?LoadSubtreeRequestNode $loadSubtreeRequestNode = null,
bool $loadChildren = false,
int $depth = 0,
Expand All @@ -250,11 +300,16 @@ private function buildNode(
? $contentInfo->getContentType()
: null;

if ($contentType !== null && $contentType->isContainer) {
$containerLocations[] = $location;
}

$limit = $this->resolveLoadLimit($loadSubtreeRequestNode);
$offset = null !== $loadSubtreeRequestNode
? $loadSubtreeRequestNode->offset
: 0;

$totalChildrenCount = 0;
$children = [];
if ($loadChildren && $depth < $this->getSetting('tree_max_depth')) {
$searchResult = $this->findSubitems($location, $limit, $offset, $sortClause, $sortOrder);
Expand All @@ -269,18 +324,14 @@ private function buildNode(
$children[] = $this->buildNode(
$childLocation,
$uninitializedContentInfoList,
$containerLocations,
$childLoadSubtreeRequestNode,
null !== $childLoadSubtreeRequestNode,
$depth + 1,
null,
Query::SORT_ASC
);
}
} else {
$totalChildrenCount = 0;
if ($contentType && $contentType->isContainer) {
$totalChildrenCount = $this->countSubitems($location);
}
}

return new Node(
Expand Down Expand Up @@ -310,4 +361,24 @@ private function supplyTranslatedContentName(Node $node, array $contentById): vo
$this->supplyTranslatedContentName($child, $contentById);
}
}

/**
* @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
*/
private function supplyChildrenCount(Node $node, ?array $aggregationResult = null): void
{
if ($node->isContainer) {
if ($aggregationResult !== null) {
$totalCount = $aggregationResult[$node->locationId] ?? 0;
} else {
$totalCount = $this->countSubitems($node->locationId);
}

$node->totalChildrenCount = $totalCount;
}

foreach ($node->children as $child) {
$this->supplyChildrenCount($child, $aggregationResult);
}
}
}