From e7634375f810e76bedf43b19ca8fdb521507d5bc Mon Sep 17 00:00:00 2001 From: berliner Date: Fri, 15 Nov 2024 22:24:25 +0100 Subject: [PATCH] HPC-9890: Allow to fetch only ids of content entities for export --- .../ncms_schema_extension.base.graphqls | 10 ++- .../GraphQL/DataProducer/ArticleExport.php | 58 ++------------ .../GraphQL/DataProducer/DocumentExport.php | 58 ++------------ .../SchemaExtension/NcmsSchemaExtension.php | 34 +++++++++ .../src/Wrappers/QueryConnection.php | 76 +++++++++++++++++++ 5 files changed, 127 insertions(+), 109 deletions(-) create mode 100644 html/modules/custom/ncms_graphql/src/Wrappers/QueryConnection.php diff --git a/html/modules/custom/ncms_graphql/graphql/ncms_schema_extension.base.graphqls b/html/modules/custom/ncms_graphql/graphql/ncms_schema_extension.base.graphqls index 63b9f0a9..2fe9b129 100644 --- a/html/modules/custom/ncms_graphql/graphql/ncms_schema_extension.base.graphqls +++ b/html/modules/custom/ncms_graphql/graphql/ncms_schema_extension.base.graphqls @@ -1,6 +1,7 @@ type ArticleList { - count: Int - items: [Article] + count: Int! + ids: [Int] + items: [Article!] } type Article { @@ -25,8 +26,9 @@ type Article { } type DocumentList { - count: Int - items: [Document] + count: Int! + ids: [Int] + items: [Document!] } type Document { diff --git a/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/DataProducer/ArticleExport.php b/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/DataProducer/ArticleExport.php index 6d8c5a0f..be0aeb83 100644 --- a/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/DataProducer/ArticleExport.php +++ b/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/DataProducer/ArticleExport.php @@ -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; @@ -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 @@ -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. @@ -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); }); } diff --git a/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/DataProducer/DocumentExport.php b/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/DataProducer/DocumentExport.php index 89915c9b..34cdab42 100644 --- a/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/DataProducer/DocumentExport.php +++ b/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/DataProducer/DocumentExport.php @@ -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; @@ -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)) { @@ -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); }); } diff --git a/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/SchemaExtension/NcmsSchemaExtension.php b/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/SchemaExtension/NcmsSchemaExtension.php index c0f1cbfd..c0f45dcf 100644 --- a/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/SchemaExtension/NcmsSchemaExtension.php +++ b/html/modules/custom/ncms_graphql/src/Plugin/GraphQL/SchemaExtension/NcmsSchemaExtension.php @@ -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; @@ -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); } /** @@ -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'), @@ -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(); + }) + ); + } + } diff --git a/html/modules/custom/ncms_graphql/src/Wrappers/QueryConnection.php b/html/modules/custom/ncms_graphql/src/Wrappers/QueryConnection.php new file mode 100644 index 00000000..d1129b88 --- /dev/null +++ b/html/modules/custom/ncms_graphql/src/Wrappers/QueryConnection.php @@ -0,0 +1,76 @@ +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(); + }); + } + +}