Skip to content

Commit

Permalink
Merge pull request #261 from UN-OCHA/berliner/HPC-9890
Browse files Browse the repository at this point in the history
HPC-9890: Allow to fetch only ids of content entities for export
  • Loading branch information
berliner authored Nov 15, 2024
2 parents 674c187 + e763437 commit 6cb5763
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 109 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
type ArticleList {
count: Int
items: [Article]
count: Int!
ids: [Int]
items: [Article!]
}

type Article {
Expand All @@ -25,8 +26,9 @@ type Article {
}

type DocumentList {
count: Int
items: [Document]
count: Int!
ids: [Int]
items: [Document!]
}

type Document {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
namespace Drupal\ncms_graphql\Plugin\GraphQL\DataProducer;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\graphql\GraphQL\Execution\FieldContext;
use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
use Drupal\ncms_graphql\Wrappers\QueryConnection;
use Drupal\node\NodeInterface;
use GraphQL\Deferred;
use Symfony\Component\DependencyInjection\ContainerInterface;
Expand Down Expand Up @@ -89,15 +89,14 @@ public function __construct(
* A promise.
*/
public function resolve(array $tags = NULL, FieldContext $context) {

return new Deferred(function () use ($tags, $context) {
$type = 'node';

// Add the list cache tags so that the cache entry is purged whenever a
// new entity of this type is saved.
$entity_type = $this->entityTypeManager->getDefinition($type);
/** @var \Drupal\Core\Entity\EntityTypeInterface $type */
$context->addCacheTags($entity_type->getListCacheTags());
$context->addCacheContexts($entity_type->getListCacheContexts());

// Load the buffered entities.
$query = $this->entityTypeManager
Expand All @@ -106,8 +105,9 @@ public function resolve(array $tags = NULL, FieldContext $context) {
$query->accessCheck(TRUE);
$query->condition('type', 'article');
$query->condition('status', NodeInterface::PUBLISHED);
$query->condition('field_tags', NULL, 'IS NOT NULL');
$query->condition('field_content_space', NULL, 'IS NOT NULL');
$query->sort('changed', 'DESC');

if (!empty($tags)) {
// Add conditions to limit to specific tags, either on the article
// itself or on the referenced content space.
Expand All @@ -125,55 +125,7 @@ public function resolve(array $tags = NULL, FieldContext $context) {
$query->condition($and_condition);
}
}
$entity_ids = $query->execute();

$entities = $entity_ids ? $this->entityTypeManager
->getStorage($type)
->loadMultiple($entity_ids) : [];

if (!$entities) {
return [];
}

foreach ($entities as $id => $entity) {
$context->addCacheableDependency($entities[$id]);

if (isset($language) && $language !== $entities[$id]->language()->getId() && $entities[$id] instanceof TranslatableInterface) {
$entities[$id] = $entities[$id]->getTranslation($language);
$entities[$id]->addCacheContexts(["static:language:{$language}"]);
}

/** @var \Drupal\Core\Access\AccessResultInterface $accessResult */
$accessResult = $entities[$id]->access('view', NULL, TRUE);
$context->addCacheableDependency($accessResult);
// We need to call isAllowed() because isForbidden() returns FALSE
// for neutral access results, which is dangerous. Only an explicitly
// allowed result means that the user has access.
if (!$accessResult->isAllowed()) {
unset($entities[$id]);
continue;
}

// Filter out articles that are not tagged yet.
if ($entities[$id]->get('field_tags')->isEmpty()) {
unset($entities[$id]);
continue;
}

// Filter out articles that are not associated to a content space.
if ($entities[$id]->get('field_content_space')->isEmpty()) {
unset($entities[$id]);
continue;
}

// @todo Filter out articles that are associated to a content space
// which has no tags. See if this is necessary.
}

return (object) [
'count' => count($entities),
'items' => $entities,
];
return new QueryConnection($query);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
namespace Drupal\ncms_graphql\Plugin\GraphQL\DataProducer;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\graphql\GraphQL\Execution\FieldContext;
use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
use Drupal\ncms_graphql\Wrappers\QueryConnection;
use Drupal\node\NodeInterface;
use GraphQL\Deferred;
use Symfony\Component\DependencyInjection\ContainerInterface;
Expand Down Expand Up @@ -96,16 +96,18 @@ public function resolve(array $tags = NULL, FieldContext $context) {
// Add the list cache tags so that the cache entry is purged whenever a
// new entity of this type is saved.
$entity_type = $this->entityTypeManager->getDefinition($type);
/** @var \Drupal\Core\Entity\EntityTypeInterface $type */
$context->addCacheTags($entity_type->getListCacheTags());
$context->addCacheContexts($entity_type->getListCacheContexts());

// Load the buffered entities.
// Build the query.
$query = $this->entityTypeManager
->getStorage($type)
->getQuery();
$query->accessCheck(TRUE);
$query->condition('type', 'document');
$query->condition('status', NodeInterface::PUBLISHED);
$query->condition('field_tags', NULL, 'IS NOT NULL');
$query->condition('field_content_space', NULL, 'IS NOT NULL');
$query->sort('changed', 'DESC');

if (!empty($tags)) {
Expand All @@ -125,55 +127,7 @@ public function resolve(array $tags = NULL, FieldContext $context) {
$query->condition($and_condition);
}
}
$entity_ids = $query->execute();

$entities = $entity_ids ? $this->entityTypeManager
->getStorage($type)
->loadMultiple($entity_ids) : [];

if (!$entities) {
return [];
}

foreach ($entities as $id => $entity) {
$context->addCacheableDependency($entities[$id]);

if (isset($language) && $language !== $entities[$id]->language()->getId() && $entities[$id] instanceof TranslatableInterface) {
$entities[$id] = $entities[$id]->getTranslation($language);
$entities[$id]->addCacheContexts(["static:language:{$language}"]);
}

/** @var \Drupal\Core\Access\AccessResultInterface $accessResult */
$accessResult = $entities[$id]->access('view', NULL, TRUE);
$context->addCacheableDependency($accessResult);
// We need to call isAllowed() because isForbidden() returns FALSE
// for neutral access results, which is dangerous. Only an explicitly
// allowed result means that the user has access.
if (!$accessResult->isAllowed()) {
unset($entities[$id]);
continue;
}

// Filter out documents that are not tagged yet.
if ($entities[$id]->get('field_tags')->isEmpty()) {
unset($entities[$id]);
continue;
}

// Filter out documents that are not associated to a content space.
if ($entities[$id]->get('field_content_space')->isEmpty()) {
unset($entities[$id]);
continue;
}

// @todo Filter out documents that are associated to a content space
// which has no tags. See if this is necessary.
}

return (object) [
'count' => count($entities),
'items' => $entities,
];
return new QueryConnection($query);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
namespace Drupal\ncms_graphql\Plugin\GraphQL\SchemaExtension;

use Drupal\graphql\GraphQL\ResolverBuilder;
use Drupal\graphql\GraphQL\ResolverRegistry;
use Drupal\graphql\GraphQL\ResolverRegistryInterface;
use Drupal\graphql\Plugin\GraphQL\SchemaExtension\SdlSchemaExtensionPluginBase;
use Drupal\ncms_graphql\Wrappers\QueryConnection;
use Drupal\node\NodeInterface;
use Drupal\paragraphs\Entity\Paragraph;
use Symfony\Component\DependencyInjection\ContainerInterface;
Expand Down Expand Up @@ -56,6 +58,9 @@ public function registerResolvers(ResolverRegistryInterface $registry): void {
$this->addFieldResolverContentSpace($registry, $builder);
$this->addFieldResolverParagraph($registry, $builder);
$this->addFieldResolverDocumentChapter($registry, $builder);

$this->addListFieldResolvers('ArticleList', $registry, $builder);
$this->addListFieldResolvers('DocumentList', $registry, $builder);
}

/**
Expand Down Expand Up @@ -105,6 +110,7 @@ private function addQueries(ResolverRegistryInterface $registry, ResolverBuilder
$builder->produce('article_export')
->map('tags', $builder->fromArgument('tags'))
);

$registry->addFieldResolver('Query', 'articleSearch',
$builder->compose(
$builder->produce('hid_user'),
Expand Down Expand Up @@ -665,4 +671,32 @@ private function addFieldResolverDocumentChapter(ResolverRegistryInterface $regi
);
}

/**
* Add field resolvers for list types using a query connection.
*
* @param string $type
* The data type.
* @param \Drupal\graphql\GraphQL\ResolverRegistry $registry
* The resolver registry.
* @param \Drupal\graphql\GraphQL\ResolverBuilder $builder
* The resolver builder.
*/
protected function addListFieldResolvers($type, ResolverRegistry $registry, ResolverBuilder $builder): void {
$registry->addFieldResolver($type, 'count',
$builder->callback(function (QueryConnection $connection) {
return $connection->count();
})
);
$registry->addFieldResolver($type, 'ids',
$builder->callback(function (QueryConnection $connection) {
return $connection->ids();
})
);
$registry->addFieldResolver($type, 'items',
$builder->callback(function (QueryConnection $connection) {
return $connection->items();
})
);
}

}
76 changes: 76 additions & 0 deletions html/modules/custom/ncms_graphql/src/Wrappers/QueryConnection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

namespace Drupal\ncms_graphql\Wrappers;

use Drupal\Core\Entity\Query\QueryInterface;
use GraphQL\Deferred;

/**
* Helper class that wraps entity queries.
*/
class QueryConnection {

/**
* The query object.
*
* @var \Drupal\Core\Entity\Query\QueryInterface
*/
protected $query;

/**
* QueryConnection constructor.
*
* @param \Drupal\Core\Entity\Query\QueryInterface $query
* The query object.
*/
public function __construct(QueryInterface $query) {
$this->query = $query;
}

/**
* Return the number of results.
*
* @return int
* The number of results.
*/
public function count() {
$query = clone $this->query;
$query->range(NULL, NULL)->count();
/** @var int */
return $query->execute();
}

/**
* Return the ids.
*
* @return int[]
* An array of ids.
*/
public function ids() {
$result = $this->query->execute();
if (empty($result)) {
return [];
}
return array_values($result);
}

/**
* Return all items.
*
* @return array|\GraphQL\Deferred
* The promise.
*/
public function items() {
$result = $this->query->execute();
if (empty($result)) {
return [];
}

$buffer = \Drupal::service('graphql.buffer.entity');
$callback = $buffer->add($this->query->getEntityTypeId(), array_values($result));
return new Deferred(function () use ($callback) {
return $callback();
});
}

}

0 comments on commit 6cb5763

Please sign in to comment.