From eaac7ac81137112738f6611210c8a4898d90f549 Mon Sep 17 00:00:00 2001 From: Lennard Date: Wed, 10 Apr 2019 18:50:22 +0200 Subject: [PATCH 1/3] feat(viewsreference): added support for viewsreference. (#22) --- composer.json | 2 +- src/Plugin/Deriver/Fields/ViewDeriver.php | 129 ------ src/Plugin/Deriver/ViewDeriverBase.php | 214 +--------- .../Entity/Fields/View/ViewDerivative.php | 141 +++++++ src/Plugin/GraphQL/Fields/View.php | 6 +- .../TypedData/ViewsContextualFilterInput.php | 28 ++ .../Scalars/TypedData/ViewsFilterInput.php | 28 ++ .../Scalars/TypedData/ViewsSortByInput.php | 28 ++ src/Plugin/GraphQL/Types/ViewResultType.php | 18 + src/Plugin/GraphQL/UnionTypes/ViewResult.php | 17 + src/ViewDeriverHelperTrait.php | 385 ++++++++++++++++++ 11 files changed, 658 insertions(+), 338 deletions(-) create mode 100644 src/Plugin/GraphQL/Fields/Entity/Fields/View/ViewDerivative.php create mode 100644 src/Plugin/GraphQL/Scalars/TypedData/ViewsContextualFilterInput.php create mode 100644 src/Plugin/GraphQL/Scalars/TypedData/ViewsFilterInput.php create mode 100644 src/Plugin/GraphQL/Scalars/TypedData/ViewsSortByInput.php create mode 100644 src/Plugin/GraphQL/UnionTypes/ViewResult.php create mode 100644 src/ViewDeriverHelperTrait.php diff --git a/composer.json b/composer.json index 44daeba..723d9ed 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "drupal/graphql-views", + "name": "drupal/graphql_views", "type": "drupal-module", "description": "Exposes your Drupal Views data model through a GraphQL schema.", "homepage": "http://drupal.org/project/graphql_views", diff --git a/src/Plugin/Deriver/Fields/ViewDeriver.php b/src/Plugin/Deriver/Fields/ViewDeriver.php index dfc00ee..fe8577b 100644 --- a/src/Plugin/Deriver/Fields/ViewDeriver.php +++ b/src/Plugin/Deriver/Fields/ViewDeriver.php @@ -3,9 +3,7 @@ namespace Drupal\graphql_views\Plugin\Deriver\Fields; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; -use Drupal\graphql\Utility\StringHelper; use Drupal\graphql_views\Plugin\Deriver\ViewDeriverBase; -use Drupal\views\Plugin\views\display\DisplayPluginInterface; use Drupal\views\Views; /** @@ -56,131 +54,4 @@ public function getDerivativeDefinitions($basePluginDefinition) { return parent::getDerivativeDefinitions($basePluginDefinition); } - /** - * Helper function to return the contextual filter argument if any exist. - * - * @param array $arguments - * The array of available arguments. - * @param $id - * The plugin derivative id. - * - * @return array - * The contextual filter argument if applicable. - */ - protected function getContextualArguments(array $arguments, $id) { - if (!empty($arguments)) { - return [ - 'contextualFilter' => [ - 'type' => StringHelper::camelCase($id, 'contextual', 'filter', 'input'), - ], - ]; - } - - return []; - } - - /** - * Helper function to retrieve the sort arguments if any are exposed. - * - * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display - * The display plugin. - * @param $id - * The plugin derivative id. - * - * @return array - * The sort arguments if any exposed sorts are available. - */ - protected function getSortArguments(DisplayPluginInterface $display, $id) { - $sorts = array_filter($display->getOption('sorts') ?: [], function ($sort) { - return $sort['exposed']; - }); - return $sorts ? [ - 'sortDirection' => [ - 'type' => 'ViewSortDirection', - 'default' => 'asc', - ], - 'sortBy' => [ - 'type' => StringHelper::camelCase($id, 'sort', 'by'), - ], - ] : []; - } - - /** - * Helper function to return the filter argument if applicable. - * - * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display - * The display plugin. - * @param $id - * The plugin derivative id. - * - * @return array - * The filter argument if any exposed filters are available. - */ - protected function getFilterArguments(DisplayPluginInterface $display, $id) { - $filters = array_filter($display->getOption('filters') ?: [], function($filter) { - return array_key_exists('exposed', $filter) && $filter['exposed']; - }); - - return !empty($filters) ? [ - 'filter' => [ - 'type' => $display->getGraphQLFilterInputName(), - ], - ] : []; - } - - /** - * Helper function to retrieve the pager arguments if the display is paged. - * - * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display - * The display plugin. - * - * @return array - * An array of pager arguments if the view display is paged. - */ - protected function getPagerArguments(DisplayPluginInterface $display) { - return $this->isPaged($display) ? [ - 'page' => ['type' => 'Int', 'default' => $this->getPagerOffset($display)], - 'pageSize' => ['type' => 'Int', 'default' => $this->getPagerLimit($display)], - ] : []; - } - - /** - * Helper function to retrieve the types that the view can be attached to. - * - * @param array $arguments - * An array containing information about the available arguments. - * @return array - * An array of additional types the view can be embedded in. - */ - protected function getTypes(array $arguments) { - $types = ['Root']; - - if (empty($arguments)) { - return $types; - } - - foreach ($arguments as $argument) { - // Depending on whether bundles are known, we expose the view field - // either on the interface (e.g. Node) or on the type (e.g. NodePage) - // level. Here we specify types managed by other graphql_* modules, - // yet we don't define these modules as dependencies. If types are not - // in the schema, the resulting GraphQL field will be attached to - // nowhere, so it won't go into the schema. - if (empty($argument['bundles']) && empty($argument['entity_type'])) { - continue; - } - - if (empty($argument['bundles'])) { - $types = array_merge($types, [StringHelper::camelCase($argument['entity_type'])]); - } - else { - $types = array_merge($types, array_map(function ($bundle) use ($argument) { - return StringHelper::camelCase($argument['entity_type'], $bundle); - }, array_keys($argument['bundles']))); - } - } - - return $types; - } - } diff --git a/src/Plugin/Deriver/ViewDeriverBase.php b/src/Plugin/Deriver/ViewDeriverBase.php index 2d62380..685088d 100644 --- a/src/Plugin/Deriver/ViewDeriverBase.php +++ b/src/Plugin/Deriver/ViewDeriverBase.php @@ -10,6 +10,7 @@ use Drupal\graphql_views\Plugin\views\row\GraphQLEntityRow; use Drupal\graphql_views\Plugin\views\row\GraphQLFieldRow; use Drupal\graphql\Utility\StringHelper; +use Drupal\graphql_views\ViewDeriverHelperTrait; use Drupal\views\Plugin\views\display\DisplayPluginInterface; use Drupal\views\ViewEntityInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -18,6 +19,9 @@ * Base class for graphql view derivers. */ abstract class ViewDeriverBase extends DeriverBase implements ContainerDeriverInterface { + use ViewDeriverHelperTrait { + getRowResolveType as private traitGetRowResolveType; + } /** * The entity type manager. * @@ -65,52 +69,6 @@ public function __construct( $this->entityTypeManager = $entityTypeManager; } - /** - * Check if a pager is configured. - * - * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display - * The display configuration. - * - * @return bool - * Flag indicating if the view is configured with a pager. - */ - protected function isPaged(DisplayPluginInterface $display) { - $pagerOptions = $display->getOption('pager'); - return isset($pagerOptions['type']) && in_array($pagerOptions['type'], ['full', 'mini']); - } - - /** - * Get the configured default limit. - * - * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display - * The display configuration. - * - * @return int - * The default limit. - */ - protected function getPagerLimit(DisplayPluginInterface $display) { - $pagerOptions = $display->getOption('pager'); - return NestedArray::getValue($pagerOptions, [ - 'options', 'items_per_page', - ]) ?: 0; - } - - /** - * Get the configured default offset. - * - * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display - * The display configuration. - * - * @return int - * The default offset. - */ - protected function getPagerOffset(DisplayPluginInterface $display) { - $pagerOptions = $display->getOption('pager'); - return NestedArray::getValue($pagerOptions, [ - 'options', 'offset', - ]) ?: 0; - } - /** * Retrieves the entity type id of an entity by its base or data table. * @@ -142,172 +100,14 @@ protected function getEntityTypeByTable($table) { * * @param \Drupal\views\ViewEntityInterface $view * The view entity. - * @param $displayId - * The id of the current display. + * @param string $displayId + * Interface plugin manager. * * @return null|string * The name of the type or NULL if the type could not be derived. */ protected function getRowResolveType(ViewEntityInterface $view, $displayId) { - /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */ - $display = $this->getViewDisplay($view, $displayId); - $rowPlugin = $display->getPlugin('row'); - - if ($rowPlugin instanceof GraphQLFieldRow) { - return StringHelper::camelCase($display->getGraphQLRowName()); - } - - if ($rowPlugin instanceof GraphQLEntityRow) { - $executable = $view->getExecutable(); - $executable->setDisplay($displayId); - - if ($entityType = $executable->getBaseEntityType()) { - $typeName = $entityType->id(); - $typeNameCamel = StringHelper::camelCase($typeName); - if ($this->interfaceExists($typeNameCamel)) { - $filters = $executable->getDisplay()->getOption('filters'); - $dataTable = $entityType->getDataTable(); - $bundleKey = $entityType->getKey('bundle'); - - foreach ($filters as $filter) { - $isBundleFilter = $filter['table'] == $dataTable && $filter['field'] == $bundleKey; - $isSingleValued = is_array($filter['value']) && count($filter['value']) == 1; - $isExposed = isset($filter['exposed']) && $filter['exposed']; - if ($isBundleFilter && $isSingleValued && !$isExposed) { - $bundle = reset($filter['value']); - $typeName .= "_$bundle"; - break; - } - } - - return StringHelper::camelCase($typeName); - } - } - - return 'Entity'; - } - - return NULL; - } - - /** - * Check if a certain interface exists. - * - * @param string $interface - * The GraphQL interface name. - * - * @return bool - * Boolean flag indicating if the interface exists. - */ - protected function interfaceExists($interface) { - return (bool) array_filter($this->interfacePluginManager->getDefinitions(), function($definition) use ($interface) { - return $definition['name'] === $interface; - }); + return $this->traitGetRowResolveType($view, $displayId, $this->interfacePluginManager); } - /** - * Returns a view display object. - * - * @param \Drupal\views\ViewEntityInterface $view - * The view object. - * @param string $displayId - * The display ID to use. - * - * @return \Drupal\views\Plugin\views\display\DisplayPluginInterface - * The view display object. - */ - protected function getViewDisplay(ViewEntityInterface $view, $displayId) { - $viewExecutable = $view->getExecutable(); - $viewExecutable->setDisplay($displayId); - return $viewExecutable->getDisplay(); - } - - /** - * Returns a view style object. - * - * @param \Drupal\views\ViewEntityInterface $view - * The view object. - * @param string $displayId - * The display ID to use. - * - * @return \Drupal\views\Plugin\views\style\StylePluginBase - * The view style object. - */ - protected function getViewStyle(ViewEntityInterface $view, $displayId) { - $viewExecutable = $view->getExecutable(); - $viewExecutable->setDisplay($displayId); - return $viewExecutable->getStyle(); - } - - /** - * Returns cache metadata plugin definitions. - * - * @param \Drupal\views\ViewEntityInterface $view - * The view object. - * - * @return array - * The cache metadata definitions for the plugin definition. - */ - protected function getCacheMetadataDefinition(ViewEntityInterface $view, DisplayPluginInterface $display) { - $metadata = $display->getCacheMetadata() - ->addCacheTags($view->getCacheTags()) - ->addCacheContexts($view->getCacheContexts()) - ->mergeCacheMaxAge($view->getCacheMaxAge()); - - return [ - 'schema_cache_tags' => $metadata->getCacheTags(), - 'schema_cache_max_age' => $metadata->getCacheMaxAge(), - 'response_cache_contexts' => array_filter($metadata->getCacheContexts(), function ($context) { - // Don't emit the url cache contexts. - return $context !== 'url' && strpos($context, 'url.') !== 0; - }), - ]; - } - - /** - * Returns information about view arguments (contextual filters). - * - * @param array $viewArguments - * The "arguments" option of a view display. - * - * @return array - * Arguments information keyed by the argument ID. Subsequent array keys: - * - index: argument index. - * - entity_type: target entity type. - * - bundles: target bundles (can be empty). - */ - protected function getArgumentsInfo(array $viewArguments) { - $argumentsInfo = []; - - $index = 0; - foreach ($viewArguments as $argumentId => $argument) { - $info = [ - 'index' => $index, - 'entity_type' => NULL, - 'bundles' => [], - ]; - - if (isset($argument['entity_type']) && isset($argument['entity_field'])) { - $entityType = $this->entityTypeManager->getDefinition($argument['entity_type']); - if ($entityType) { - $idField = $entityType->getKey('id'); - if ($idField === $argument['entity_field']) { - $info['entity_type'] = $argument['entity_type']; - if ( - $argument['specify_validation'] && - strpos($argument['validate']['type'], 'entity:') === 0 && - !empty($argument['validate_options']['bundles']) - ) { - $info['bundles'] = $argument['validate_options']['bundles']; - } - } - } - } - - $argumentsInfo[$argumentId] = $info; - $index++; - } - - return $argumentsInfo; - } } diff --git a/src/Plugin/GraphQL/Fields/Entity/Fields/View/ViewDerivative.php b/src/Plugin/GraphQL/Fields/Entity/Fields/View/ViewDerivative.php new file mode 100644 index 0000000..d582f5d --- /dev/null +++ b/src/Plugin/GraphQL/Fields/Entity/Fields/View/ViewDerivative.php @@ -0,0 +1,141 @@ +getValue(); + $this->pluginDefinition['view'] = $values['target_id']; + $this->pluginDefinition['display'] = $values['display_id']; + $view = EntityView::load($values['target_id']); + $display = $this->getViewDisplay($view, $values['display_id']); + $this->pluginDefinition['paged'] = $this->isPaged($display); + $this->pluginDefinition['arguments_info'] = $this->getArgumentsInfo($display->getOption('arguments') ?: []); + $this->pluginDefinition = array_merge($this->pluginDefinition, $this->getCacheMetadataDefinition($view, $display)); + $this->setOverridenViewDefaults($value, $args); + $this->setViewDefaultValues($display, $args); + return parent::resolveValues($value, $args, $context, $info); + } + + /** + * Get configuration values from views reference field. + * + * @param mixed $value + * The current object value. + * + * @return array|mixed + * Return unserialized data. + */ + protected function getViewReferenceConfiguration($value) { + $values = $value->getValue(); + return isset($values['data']) ? unserialize($values['data']) : []; + } + + /** + * Set default display settings. + * + * @param mixed $value + * The current object value. + * @param array $args + * Arguments where the default view settings needs to be added. + */ + protected function setOverridenViewDefaults($value, array &$args) { + $viewReferenceConfiguration = $this->getViewReferenceConfiguration($value); + if (!empty($viewReferenceConfiguration['pager'])) { + $this->pluginDefinition['paged'] = in_array($viewReferenceConfiguration['pager'], [ + 'full', + 'mini', + ]); + } + + if (!isset($args['pageSize']) && !empty($viewReferenceConfiguration['limit'])) { + $args['pageSize'] = $viewReferenceConfiguration['limit']; + } + + if (!isset($args['offset']) && !empty($viewReferenceConfiguration['offset'])) { + $args['offset'] = $viewReferenceConfiguration['offset']; + } + + /* Expected format: {"contextualFilter": {"key": "value","keyN": "valueN"}} */ + if (!isset($args['contextualFilter']) && !empty($viewReferenceConfiguration['argument'])) { + $argument = json_decode($viewReferenceConfiguration['argument'], TRUE); + if (isset($argument['contextualFilter']) && !empty($argument['contextualFilter'])) { + $args['contextualFilter'] = $argument['contextualFilter']; + } + } + } + + /** + * Set default display settings. + * + * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display + * The display configuration. + * @param array $args + * Arguments where the default view settings needs to be added. + */ + protected function setViewDefaultValues(DisplayPluginInterface $display, array &$args) { + if (!isset($args['pageSize']) && $this->pluginDefinition['paged']) { + $args['pageSize'] = $this->getPagerLimit($display); + } + if (!isset($args['page']) && $this->pluginDefinition['paged']) { + $args['page'] = $this->getPagerOffset($display); + } + } + +} diff --git a/src/Plugin/GraphQL/Fields/View.php b/src/Plugin/GraphQL/Fields/View.php index ebc78d9..4b4aa25 100644 --- a/src/Plugin/GraphQL/Fields/View.php +++ b/src/Plugin/GraphQL/Fields/View.php @@ -70,7 +70,7 @@ public function resolveValues($value, array $args, ResolveContext $context, Reso $executable->setDisplay($definition['display']); // Set view contextual filters. - /* @see \Drupal\graphql_core\Plugin\Deriver\ViewDeriverBase::getArgumentsInfo() */ + /* @see \Drupal\graphql_views\ViewDeriverHelperTrait::getArgumentsInfo() */ if (!empty($definition['arguments_info'])) { $arguments = $this->extractContextualFilters($value, $args); $executable->setArguments($arguments); @@ -92,6 +92,10 @@ public function resolveValues($value, array $args, ResolveContext $context, Reso $executable->setCurrentPage($args['page']); } + if (isset($args['offset']) && !empty($args['offset'])) { + $executable->setOffset($args['offset']); + } + $result = $executable->render($definition['display']); /** @var \Drupal\Core\Cache\CacheableMetadata $cache */ if ($cache = $result['cache']) { diff --git a/src/Plugin/GraphQL/Scalars/TypedData/ViewsContextualFilterInput.php b/src/Plugin/GraphQL/Scalars/TypedData/ViewsContextualFilterInput.php new file mode 100644 index 0000000..9f2c950 --- /dev/null +++ b/src/Plugin/GraphQL/Scalars/TypedData/ViewsContextualFilterInput.php @@ -0,0 +1,28 @@ +pluginDefinition['view'] === $view->id() && $this->pluginDefinition['display'] == $view->current_display) { + return TRUE; + } + } + + return parent::applies($object, $context, $info); + } + } diff --git a/src/Plugin/GraphQL/UnionTypes/ViewResult.php b/src/Plugin/GraphQL/UnionTypes/ViewResult.php new file mode 100644 index 0000000..85ee20f --- /dev/null +++ b/src/Plugin/GraphQL/UnionTypes/ViewResult.php @@ -0,0 +1,17 @@ + [ + 'type' => StringHelper::camelCase($id, 'contextual', 'filter', 'input'), + ], + ]; + } + + return []; + } + + /** + * Helper function to retrieve the sort arguments if any are exposed. + * + * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display + * The display plugin. + * @param string $id + * The plugin derivative id. + * + * @return array + * The sort arguments if any exposed sorts are available. + */ + protected function getSortArguments(DisplayPluginInterface $display, $id) { + $sorts = array_filter($display->getOption('sorts') ?: [], function ($sort) { + return $sort['exposed']; + }); + return $sorts ? [ + 'sortDirection' => [ + 'type' => 'ViewSortDirection', + 'default' => 'asc', + ], + 'sortBy' => [ + 'type' => StringHelper::camelCase($id, 'sort', 'by'), + ], + ] : []; + } + + /** + * Helper function to return the filter argument if applicable. + * + * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display + * The display plugin. + * @param string $id + * The plugin derivative id. + * + * @return array + * The filter argument if any exposed filters are available. + */ + protected function getFilterArguments(DisplayPluginInterface $display, $id) { + $filters = array_filter($display->getOption('filters') ?: [], function ($filter) { + return array_key_exists('exposed', $filter) && $filter['exposed']; + }); + + return !empty($filters) ? [ + 'filter' => [ + 'type' => $display->getGraphQLFilterInputName(), + ], + ] : []; + } + + /** + * Helper function to retrieve the pager arguments if the display is paged. + * + * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display + * The display plugin. + * + * @return array + * An array of pager arguments if the view display is paged. + */ + protected function getPagerArguments(DisplayPluginInterface $display) { + return $this->isPaged($display) ? [ + 'page' => ['type' => 'Int', 'default' => $this->getPagerOffset($display)], + 'pageSize' => [ + 'type' => 'Int', + 'default' => $this->getPagerLimit($display), + ], + ] : []; + } + + /** + * Helper function to retrieve the types that the view can be attached to. + * + * @param array $arguments + * An array containing information about the available arguments. + * @param array $types + * Types where it needs to be added. + * + * @return array + * An array of additional types the view can be embedded in. + */ + protected function getTypes(array $arguments, array $types = ['Root']) { + + if (empty($arguments)) { + return $types; + } + + foreach ($arguments as $argument) { + // Depending on whether bundles are known, we expose the view field + // either on the interface (e.g. Node) or on the type (e.g. NodePage) + // level. Here we specify types managed by other graphql_* modules, + // yet we don't define these modules as dependencies. If types are not + // in the schema, the resulting GraphQL field will be attached to + // nowhere, so it won't go into the schema. + if (empty($argument['bundles']) && empty($argument['entity_type'])) { + continue; + } + + if (empty($argument['bundles'])) { + $types = array_merge($types, [StringHelper::camelCase($argument['entity_type'])]); + } + else { + $types = array_merge($types, array_map(function ($bundle) use ($argument) { + return StringHelper::camelCase($argument['entity_type'], $bundle); + }, array_keys($argument['bundles']))); + } + } + + return $types; + } + + /** + * Check if a pager is configured. + * + * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display + * The display configuration. + * + * @return bool + * Flag indicating if the view is configured with a pager. + */ + protected function isPaged(DisplayPluginInterface $display) { + $pagerOptions = $display->getOption('pager'); + return isset($pagerOptions['type']) && in_array($pagerOptions['type'], [ + 'full', + 'mini', + ]); + } + + /** + * Returns a view display object. + * + * @param \Drupal\views\ViewEntityInterface $view + * The view object. + * @param string $displayId + * The display ID to use. + * + * @return \Drupal\views\Plugin\views\display\DisplayPluginInterface + * The view display object. + */ + protected function getViewDisplay(ViewEntityInterface $view, $displayId) { + $viewExecutable = $view->getExecutable(); + $viewExecutable->setDisplay($displayId); + return $viewExecutable->getDisplay(); + } + + /** + * Get the configured default limit. + * + * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display + * The display configuration. + * + * @return int + * The default limit. + */ + protected function getPagerLimit(DisplayPluginInterface $display) { + $pagerOptions = $display->getOption('pager'); + return NestedArray::getValue($pagerOptions, [ + 'options', + 'items_per_page', + ]) ?: 0; + } + + /** + * Get the configured default offset. + * + * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display + * The display configuration. + * + * @return int + * The default offset. + */ + protected function getPagerOffset(DisplayPluginInterface $display) { + $pagerOptions = $display->getOption('pager'); + return NestedArray::getValue($pagerOptions, [ + 'options', + 'offset', + ]) ?: 0; + } + + /** + * Check if a certain interface exists. + * + * @param string $interface + * The GraphQL interface name. + * @param \Drupal\Component\Plugin\PluginManagerInterface $interfacePluginManager + * Plugin interface manager. + * + * @return bool + * Boolean flag indicating if the interface exists. + */ + protected function interfaceExists($interface, PluginManagerInterface $interfacePluginManager) { + return (bool) array_filter($interfacePluginManager->getDefinitions(), function ($definition) use ($interface) { + return $definition['name'] === $interface; + }); + } + + /** + * Retrieves the type the view's rows resolve to. + * + * @param \Drupal\views\ViewEntityInterface $view + * The view entity. + * @param string $displayId + * The id of the current display. + * @param \Drupal\Component\Plugin\PluginManagerInterface $interfacePluginManager + * Interface plugin manager. + * + * @return null|string + * The name of the type or NULL if the type could not be derived. + */ + protected function getRowResolveType(ViewEntityInterface $view, $displayId, PluginManagerInterface $interfacePluginManager) { + /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */ + $display = $this->getViewDisplay($view, $displayId); + $rowPlugin = $display->getPlugin('row'); + + if ($rowPlugin instanceof GraphQLFieldRow) { + return StringHelper::camelCase($display->getGraphQLRowName()); + } + + if ($rowPlugin instanceof GraphQLEntityRow) { + $executable = $view->getExecutable(); + $executable->setDisplay($displayId); + + if ($entityType = $executable->getBaseEntityType()) { + $typeName = $entityType->id(); + $typeNameCamel = StringHelper::camelCase($typeName); + if ($this->interfaceExists($typeNameCamel, $interfacePluginManager)) { + $filters = $executable->getDisplay()->getOption('filters'); + $dataTable = $entityType->getDataTable(); + $bundleKey = $entityType->getKey('bundle'); + + foreach ($filters as $filter) { + $isBundleFilter = $filter['table'] == $dataTable && $filter['field'] == $bundleKey; + $isSingleValued = is_array($filter['value']) && count($filter['value']) == 1; + $isExposed = isset($filter['exposed']) && $filter['exposed']; + if ($isBundleFilter && $isSingleValued && !$isExposed) { + $bundle = reset($filter['value']); + $typeName .= "_$bundle"; + break; + } + } + + return StringHelper::camelCase($typeName); + } + } + + return 'Entity'; + } + + return NULL; + } + + /** + * Returns a view style object. + * + * @param \Drupal\views\ViewEntityInterface $view + * The view object. + * @param string $displayId + * The display ID to use. + * + * @return \Drupal\views\Plugin\views\style\StylePluginBase + * The view style object. + */ + protected function getViewStyle(ViewEntityInterface $view, $displayId) { + $viewExecutable = $view->getExecutable(); + $viewExecutable->setDisplay($displayId); + return $viewExecutable->getStyle(); + } + + /** + * Returns cache metadata plugin definitions. + * + * @param \Drupal\views\ViewEntityInterface $view + * The view object. + * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display + * The view display. + * + * @return array + * The cache metadata definitions for the plugin definition. + */ + protected function getCacheMetadataDefinition(ViewEntityInterface $view, DisplayPluginInterface $display) { + $metadata = $display->getCacheMetadata() + ->addCacheTags($view->getCacheTags()) + ->addCacheContexts($view->getCacheContexts()) + ->mergeCacheMaxAge($view->getCacheMaxAge()); + + return [ + 'schema_cache_tags' => $metadata->getCacheTags(), + 'schema_cache_max_age' => $metadata->getCacheMaxAge(), + 'response_cache_contexts' => array_filter($metadata->getCacheContexts(), function ($context) { + // Don't emit the url cache contexts. + return $context !== 'url' && strpos($context, 'url.') !== 0; + }), + ]; + } + + /** + * Returns information about view arguments (contextual filters). + * + * @param array $viewArguments + * The "arguments" option of a view display. + * + * @return array + * Arguments information keyed by the argument ID. Subsequent array keys: + * - index: argument index. + * - entity_type: target entity type. + * - bundles: target bundles (can be empty). + * + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + protected function getArgumentsInfo(array $viewArguments) { + $argumentsInfo = []; + /* @var \Drupal\Core\Entity\EntityTypeManager $entityTypeManager */ + $entityTypeManager = \Drupal::service('entity_type.manager'); + + $index = 0; + foreach ($viewArguments as $argumentId => $argument) { + $info = [ + 'index' => $index, + 'entity_type' => NULL, + 'bundles' => [], + ]; + + if (isset($argument['entity_type']) && isset($argument['entity_field'])) { + $entityType = $entityTypeManager->getDefinition($argument['entity_type']); + if ($entityType) { + $idField = $entityType->getKey('id'); + if ($idField === $argument['entity_field']) { + $info['entity_type'] = $argument['entity_type']; + if ( + $argument['specify_validation'] && + strpos($argument['validate']['type'], 'entity:') === 0 && + !empty($argument['validate_options']['bundles']) + ) { + $info['bundles'] = $argument['validate_options']['bundles']; + } + } + } + } + + $argumentsInfo[$argumentId] = $info; + $index++; + } + + return $argumentsInfo; + } + +} From 289d0b7a3d22978b886603efd3bbfe3df9320ebe Mon Sep 17 00:00:00 2001 From: Maria Comas Date: Fri, 1 Nov 2019 13:40:10 +0100 Subject: [PATCH 2/3] Update docs. (#28) Adapted from: - https://www.amazeelabs.com/en/blog/graphql-drupalers-part-4-fetching-entities - https://www.amazeelabs.com/en/blog/drupal-graphql-batteries-included --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe0fd56..e649493 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,22 @@ [![Code Coverage](https://img.shields.io/codecov/c/github/drupal-graphql/graphql-views.svg)](https://codecov.io/gh/drupal-graphql/graphql-views) [![Code Quality](https://img.shields.io/scrutinizer/g/drupal-graphql/graphql-views.svg)](https://scrutinizer-ci.com/g/drupal-graphql/graphql-views/?branch=8.x-1.x) -Please refer to the main [Drupal GraphQL] module for further information. - [Drupal GraphQL]: https://github.com/drupal-graphql/graphql + +With `graphql_views` enabled a `GraphQL` views display can be added to any view in the system. + +Results can be sorted, filtered based on content fields, and relationships can be added. There is also the option to return either the full entities, just a selection of fields, or even search results taken straight from a search server. + +Any `GraphQL` views display will provide a field that will adapt to the views configuration: + +- The fields name will be composed of the views and displays machine names or configured manually. +- If the view is configured with pagination, the field will accept pager arguments and return the result list and count field instead of the entity list directly. +- Any exposed filters will be added to the `filters` input type that can be used to pass filter values into the view. +- Any contextual filters will be added to the `contextual_filters` input type. +- If a contextual filters validation criteria match an existing GraphQL type, the field will be added to this type too, and the value will be populated from the current result context. + +Read more on: +- https://www.amazeelabs.com/en/blog/graphql-drupalers-part-4-fetching-entities +- https://www.amazeelabs.com/en/blog/drupal-graphql-batteries-included + +Please also refer to the main [Drupal GraphQL] module for further information. From a78f8fd513b1aa1f103ef743a3f9d4332ae70236 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Mon, 4 Nov 2019 08:20:20 +0100 Subject: [PATCH 3/3] Update in alignment with GraphQL 3.0 (#32) --- .travis.yml | 42 ++++++++++++++++-------------- tests/src/Kernel/ViewsTest.php | 12 ++++----- tests/src/Kernel/ViewsTestBase.php | 9 +++---- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7f71645..79ff593 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,13 @@ language: php sudo: false php: + - 7.3 + - 7.2 - 7.1 - - 7 - - 5.6 + - 7.0 + +services: + - mysql env: global: @@ -13,24 +17,23 @@ env: - SIMPLETEST_DB=mysql://root:@127.0.0.1/graphql - TRAVIS=true matrix: - - DRUPAL_CORE=8.4.x - - DRUPAL_CORE=8.5.x - - DRUPAL_CORE=8.6.x + - DRUPAL_CORE=8.7.x + - DRUPAL_CORE=8.8.x matrix: # Don't wait for the allowed failures to build. fast_finish: true include: - - php: 7.1 + - php: 7.3 env: - - DRUPAL_CORE=8.6.x + - DRUPAL_CORE=8.7.x # Only run code coverage on the latest php and drupal versions. - WITH_PHPDBG_COVERAGE=true allow_failures: # Allow the code coverage report to fail. - - php: 7.1 + - php: 7.3 env: - - DRUPAL_CORE=8.6.x + - DRUPAL_CORE=8.7.x # Only run code coverage on the latest php and drupal versions. - WITH_PHPDBG_COVERAGE=true @@ -54,14 +57,6 @@ before_install: else export PHPINI=$HOME/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi - # PHP Deprecated: Automatically populating $HTTP_RAW_POST_DATA is deprecated - # and will be removed in a future version. To avoid this warning set - # 'always_populate_raw_post_data' to '-1' in php.ini and use the php://input - # stream instead. - - if [[ "$TRAVIS_PHP_VERSION" == "5.6" ]]; - then echo always_populate_raw_post_data = -1 >> $PHPINI; - fi; - # Disable the default memory limit. - echo memory_limit = -1 >> $PHPINI @@ -92,10 +87,17 @@ install: # Bring in the module dependencies without requiring a merge plugin. The # require also triggers a full 'composer install'. - - composer --working-dir=$DRUPAL_BUILD_DIR require webonyx/graphql-php:^0.12.6 + - composer --working-dir=$DRUPAL_BUILD_DIR require webonyx/graphql-php:^0.12.5 + + # For Drupal < 8.8 we have to manually upgrade zend-stdlib to avoid PHP 7.3 + # incompatibilities. + - if [[ "$DRUPAL_CORE" = "8.6.x" || "$DRUPAL_CORE" = "8.7.x" ]]; + then composer --working-dir=$DRUPAL_BUILD_DIR require zendframework/zend-stdlib:3.2.1; + fi - # Update PHPUnit for Drupal version >= 8.5.0 - - if [[ "$DRUPAL_CORE" != "8.4.x" ]]; + # For Drupal < 8.8 we have to manually upgrade phpunit to avoid PHP 7.3 + # incompatibilities. + - if [[ "$DRUPAL_CORE" = "8.6.x" || "$DRUPAL_CORE" = "8.7.x" ]]; then composer --working-dir=$DRUPAL_BUILD_DIR run-script drupal-phpunit-upgrade; fi diff --git a/tests/src/Kernel/ViewsTest.php b/tests/src/Kernel/ViewsTest.php index 0e56f38..add0dd1 100644 --- a/tests/src/Kernel/ViewsTest.php +++ b/tests/src/Kernel/ViewsTest.php @@ -43,7 +43,7 @@ public function testSimpleView() { 'node:2', 'node:3', 'node_list', - ])); + ])->addCacheContexts(['user'])); } /** @@ -90,7 +90,7 @@ public function testPagedView() { 'node:8', 'node:9', 'node_list', - ])); + ])->addCacheContexts(['user'])); } /** @@ -145,7 +145,7 @@ public function testSortedView() { 'node:8', 'node:9', 'node_list', - ])); + ])->addCacheContexts(['user'])); } /** @@ -177,7 +177,7 @@ public function testFilteredView() { 'node:4', 'node:7', 'node_list', - ])); + ])->addCacheContexts(['user'])); } /** @@ -214,7 +214,7 @@ public function testMultiValueFilteredView() { 'node:8', 'node_list', - ])); + ])->addCacheContexts(['user'])); } /** @@ -256,7 +256,7 @@ public function testComplexFilteredView() { 'node:8', 'node:9', 'node_list', - ])); + ])->addCacheContexts(['user'])); } /** diff --git a/tests/src/Kernel/ViewsTestBase.php b/tests/src/Kernel/ViewsTestBase.php index 28ee737..ca35782 100644 --- a/tests/src/Kernel/ViewsTestBase.php +++ b/tests/src/Kernel/ViewsTestBase.php @@ -2,14 +2,12 @@ namespace Drupal\Tests\graphql_views\Kernel; -use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait; -use Drupal\simpletest\ContentTypeCreationTrait; -use Drupal\simpletest\NodeCreationTrait; use Drupal\taxonomy\Entity\Term; use Drupal\taxonomy\Entity\Vocabulary; -use Drupal\user\Entity\Role; +use Drupal\Tests\field\Traits\EntityReferenceTestTrait; use Drupal\Tests\graphql_core\Kernel\GraphQLContentTestBase; -use Drupal\Tests\taxonomy\Functional\TaxonomyTestTrait; +use Drupal\Tests\node\Traits\ContentTypeCreationTrait; +use Drupal\Tests\node\Traits\NodeCreationTrait; /** * Base class for test views support in GraphQL. @@ -20,7 +18,6 @@ abstract class ViewsTestBase extends GraphQLContentTestBase { use NodeCreationTrait; use ContentTypeCreationTrait; use EntityReferenceTestTrait; - use TaxonomyTestTrait; /** * {@inheritdoc}