From c5a2d3fdc309dc42a2c948305f3737174677e18d Mon Sep 17 00:00:00 2001 From: Christian Fritsch <chr.fritsch@gmx.net> Date: Mon, 7 Jun 2021 14:55:05 +0200 Subject: [PATCH 01/10] GraphQL 4.x port --- .scrutinizer.yml | 6 - .travis.yml | 118 --- README.md | 25 - composer.json | 8 - config/schema/graphql_views.views.schema.yml | 7 - graphql_views.info.yml | 6 +- phpcs.xml.dist | 16 - .../Deriver/Enums/ViewSortByDeriver.php | 51 -- src/Plugin/Deriver/Fields/ViewDeriver.php | 57 -- .../Deriver/Fields/ViewResultCountDeriver.php | 47 -- .../Deriver/Fields/ViewResultListDeriver.php | 47 -- .../Deriver/Fields/ViewRowFieldDeriver.php | 54 -- .../ViewContextualFilterInputDeriver.php | 52 -- .../InputTypes/ViewFilterInputDeriver.php | 141 ---- .../Deriver/Types/ViewResultTypeDeriver.php | 43 -- .../Deriver/Types/ViewRowTypeDeriver.php | 49 -- src/Plugin/Deriver/ViewDeriverBase.php | 113 --- src/Plugin/GraphQL/DataProducer/Views.php | 127 +++ src/Plugin/GraphQL/Enums/ViewSortBy.php | 16 - .../GraphQL/Enums/ViewSortDirection.php | 32 - .../Entity/Fields/View/ViewDerivative.php | 141 ---- src/Plugin/GraphQL/Fields/View.php | 197 ----- src/Plugin/GraphQL/Fields/ViewResultCount.php | 33 - src/Plugin/GraphQL/Fields/ViewResultList.php | 33 - src/Plugin/GraphQL/Fields/ViewRowField.php | 31 - .../InputTypes/ViewContextualFilterInput.php | 18 - .../GraphQL/InputTypes/ViewFilterInput.php | 18 - .../TypedData/ViewsContextualFilterInput.php | 28 - .../Scalars/TypedData/ViewsFilterInput.php | 28 - .../Scalars/TypedData/ViewsSortByInput.php | 28 - src/Plugin/GraphQL/Types/ViewResultType.php | 36 - src/Plugin/GraphQL/Types/ViewRowType.php | 18 - src/Plugin/GraphQL/UnionTypes/ViewResult.php | 17 - src/Plugin/views/display/GraphQL.php | 142 +--- src/Plugin/views/exposed_form/GraphQL.php | 1 + src/Plugin/views/row/GraphQLEntityRow.php | 91 +-- src/Plugin/views/row/GraphQLFieldRow.php | 234 ------ src/Plugin/views/style/GraphQL.php | 1 + src/ViewDeriverHelperTrait.php | 385 ---------- .../views.view.graphql_bundle_test.yml | 203 ----- .../install/views.view.graphql_test.yml | 720 ------------------ .../graphql_views_test.features.yml | 1 - .../graphql_views_test.info.yml | 12 - .../graphql_views_test.module | 12 - tests/queries/contextual.gql | 151 ---- tests/queries/paged.gql | 29 - tests/queries/simple.gql | 7 - tests/queries/single_bundle_filter.gql | 7 - tests/queries/sorted.gql | 31 - tests/src/Kernel/ContextualViewsTest.php | 114 --- tests/src/Kernel/ViewsTest.php | 282 ------- tests/src/Kernel/ViewsTestBase.php | 88 --- 52 files changed, 180 insertions(+), 3972 deletions(-) delete mode 100644 .scrutinizer.yml delete mode 100644 .travis.yml delete mode 100644 README.md delete mode 100644 composer.json delete mode 100644 config/schema/graphql_views.views.schema.yml delete mode 100644 phpcs.xml.dist delete mode 100644 src/Plugin/Deriver/Enums/ViewSortByDeriver.php delete mode 100644 src/Plugin/Deriver/Fields/ViewDeriver.php delete mode 100644 src/Plugin/Deriver/Fields/ViewResultCountDeriver.php delete mode 100644 src/Plugin/Deriver/Fields/ViewResultListDeriver.php delete mode 100644 src/Plugin/Deriver/Fields/ViewRowFieldDeriver.php delete mode 100644 src/Plugin/Deriver/InputTypes/ViewContextualFilterInputDeriver.php delete mode 100644 src/Plugin/Deriver/InputTypes/ViewFilterInputDeriver.php delete mode 100644 src/Plugin/Deriver/Types/ViewResultTypeDeriver.php delete mode 100644 src/Plugin/Deriver/Types/ViewRowTypeDeriver.php delete mode 100644 src/Plugin/Deriver/ViewDeriverBase.php create mode 100644 src/Plugin/GraphQL/DataProducer/Views.php delete mode 100644 src/Plugin/GraphQL/Enums/ViewSortBy.php delete mode 100644 src/Plugin/GraphQL/Enums/ViewSortDirection.php delete mode 100644 src/Plugin/GraphQL/Fields/Entity/Fields/View/ViewDerivative.php delete mode 100644 src/Plugin/GraphQL/Fields/View.php delete mode 100644 src/Plugin/GraphQL/Fields/ViewResultCount.php delete mode 100644 src/Plugin/GraphQL/Fields/ViewResultList.php delete mode 100644 src/Plugin/GraphQL/Fields/ViewRowField.php delete mode 100644 src/Plugin/GraphQL/InputTypes/ViewContextualFilterInput.php delete mode 100644 src/Plugin/GraphQL/InputTypes/ViewFilterInput.php delete mode 100644 src/Plugin/GraphQL/Scalars/TypedData/ViewsContextualFilterInput.php delete mode 100644 src/Plugin/GraphQL/Scalars/TypedData/ViewsFilterInput.php delete mode 100644 src/Plugin/GraphQL/Scalars/TypedData/ViewsSortByInput.php delete mode 100644 src/Plugin/GraphQL/Types/ViewResultType.php delete mode 100644 src/Plugin/GraphQL/Types/ViewRowType.php delete mode 100644 src/Plugin/GraphQL/UnionTypes/ViewResult.php delete mode 100644 src/Plugin/views/row/GraphQLFieldRow.php delete mode 100644 src/ViewDeriverHelperTrait.php delete mode 100644 tests/modules/graphql_views_test/config/install/views.view.graphql_bundle_test.yml delete mode 100644 tests/modules/graphql_views_test/config/install/views.view.graphql_test.yml delete mode 100644 tests/modules/graphql_views_test/graphql_views_test.features.yml delete mode 100644 tests/modules/graphql_views_test/graphql_views_test.info.yml delete mode 100644 tests/modules/graphql_views_test/graphql_views_test.module delete mode 100644 tests/queries/contextual.gql delete mode 100644 tests/queries/paged.gql delete mode 100644 tests/queries/simple.gql delete mode 100644 tests/queries/single_bundle_filter.gql delete mode 100644 tests/queries/sorted.gql delete mode 100644 tests/src/Kernel/ContextualViewsTest.php delete mode 100644 tests/src/Kernel/ViewsTest.php delete mode 100644 tests/src/Kernel/ViewsTestBase.php diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 6620688..0000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,6 +0,0 @@ -filter: - excluded_paths: - - 'tests/*' - -checks: - php: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 79ff593..0000000 --- a/.travis.yml +++ /dev/null @@ -1,118 +0,0 @@ -language: php -sudo: false - -php: - - 7.3 - - 7.2 - - 7.1 - - 7.0 - -services: - - mysql - -env: - global: - - DRUPAL_GRAPHQL=8.x-3.x - - DRUPAL_BUILD_DIR=$TRAVIS_BUILD_DIR/../drupal - - SIMPLETEST_DB=mysql://root:@127.0.0.1/graphql - - TRAVIS=true - matrix: - - 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.3 - env: - - 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.3 - env: - - DRUPAL_CORE=8.7.x - # Only run code coverage on the latest php and drupal versions. - - WITH_PHPDBG_COVERAGE=true - -mysql: - database: graphql - username: root - encoding: utf8 - -# Cache composer downloads. -cache: - directories: - - $HOME/.composer - -before_install: - # Disable xdebug. - - phpenv config-rm xdebug.ini - - # Determine the php settings file location. - - if [[ $TRAVIS_PHP_VERSION = hhvm* ]]; - then export PHPINI=/etc/hhvm/php.ini; - else export PHPINI=$HOME/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; - fi - - # Disable the default memory limit. - - echo memory_limit = -1 >> $PHPINI - - # Update composer. - - composer self-update - -install: - # Create the database. - - mysql -e 'create database graphql' - - # Download Drupal 8 core from the Github mirror because it is faster. - - git clone --branch $DRUPAL_CORE --depth 1 https://github.com/drupal/drupal.git $DRUPAL_BUILD_DIR - - git clone --branch $DRUPAL_GRAPHQL --depth 1 https://github.com/drupal-graphql/graphql.git $DRUPAL_BUILD_DIR/modules/graphql - - # Reference the module in the build site. - - ln -s $TRAVIS_BUILD_DIR $DRUPAL_BUILD_DIR/modules/graphql_views - - # Copy the customized phpunit configuration file to the core directory so - # the relative paths are correct. - - cp $DRUPAL_BUILD_DIR/modules/graphql/phpunit.xml.dist $DRUPAL_BUILD_DIR/core/phpunit.xml - - # When running with phpdbg we need to replace all code occurrences that check - # for 'cli' with 'phpdbg'. Some files might be write protected, hence the - # fallback. - - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]]; - then grep -rl 'cli' $DRUPAL_BUILD_DIR/core $DRUPAL_BUILD_DIR/modules | xargs sed -i "s/'cli'/'phpdbg'/g" || true; - fi - - # 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.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 - - # 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 - -script: - # Run the unit tests using phpdbg if the environment variable is 'true'. - - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]]; - then phpdbg -qrr $DRUPAL_BUILD_DIR/vendor/bin/phpunit --configuration $DRUPAL_BUILD_DIR/core/phpunit.xml --coverage-clover $TRAVIS_BUILD_DIR/coverage.xml $TRAVIS_BUILD_DIR; - fi - - # Run the unit tests with standard php otherwise. - - if [[ "$WITH_PHPDBG_COVERAGE" != "true" ]]; - then $DRUPAL_BUILD_DIR/vendor/bin/phpunit --configuration $DRUPAL_BUILD_DIR/core/phpunit.xml $TRAVIS_BUILD_DIR; - fi - -after_success: - - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]]; - then bash <(curl -s https://codecov.io/bash); - fi diff --git a/README.md b/README.md deleted file mode 100644 index e649493..0000000 --- a/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# GraphQL Views for Drupal - -[](https://travis-ci.org/drupal-graphql/graphql-views) -[](https://codecov.io/gh/drupal-graphql/graphql-views) -[](https://scrutinizer-ci.com/g/drupal-graphql/graphql-views/?branch=8.x-1.x) - -[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. diff --git a/composer.json b/composer.json deleted file mode 100644 index 723d9ed..0000000 --- a/composer.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "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", - "license": "GPL-2.0+", - "minimum-stability": "dev" -} diff --git a/config/schema/graphql_views.views.schema.yml b/config/schema/graphql_views.views.schema.yml deleted file mode 100644 index 3c55ce4..0000000 --- a/config/schema/graphql_views.views.schema.yml +++ /dev/null @@ -1,7 +0,0 @@ -views.display.graphql: - type: views_display - label: 'GraphQL display options' - mapping: - graphql_query_name: - type: string - label: 'GraphQL query name' diff --git a/graphql_views.info.yml b/graphql_views.info.yml index 9364b5d..eff160d 100644 --- a/graphql_views.info.yml +++ b/graphql_views.info.yml @@ -2,9 +2,7 @@ name: GraphQL Views type: module description: 'Adds support for views.' package: GraphQL -core: 8.x -core_version_requirement: ^8 || ^9 +core_version_requirement: ^8.9 || ^9 dependencies: - - graphql:graphql_core + - drupal:graphql - drupal:views - - drupal:system (>=8.4) \ No newline at end of file diff --git a/phpcs.xml.dist b/phpcs.xml.dist deleted file mode 100644 index 35dff3c..0000000 --- a/phpcs.xml.dist +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ruleset name="graphql"> - <description>Default PHP CodeSniffer configuration for GraphQL.</description> - <file>.</file> - - <arg name="extensions" value="inc,install,module,php,profile,test,theme"/> - - <rule ref="Drupal.NamingConventions.ValidVariableName.LowerCamelName"> - <!-- Annotations must use the same property names as in the configuration. --> - <exclude-pattern>src/Annotation</exclude-pattern> - <exclude-pattern>src/Core/Annotation</exclude-pattern> - </rule> - - <!-- We always want short array syntax only. --> - <rule ref="Generic.Arrays.DisallowLongArraySyntax" /> -</ruleset> \ No newline at end of file diff --git a/src/Plugin/Deriver/Enums/ViewSortByDeriver.php b/src/Plugin/Deriver/Enums/ViewSortByDeriver.php deleted file mode 100644 index 4851f1f..0000000 --- a/src/Plugin/Deriver/Enums/ViewSortByDeriver.php +++ /dev/null @@ -1,51 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\Deriver\Enums; - -use Drupal\graphql\Utility\StringHelper; -use Drupal\graphql_views\Plugin\Deriver\ViewDeriverBase; -use Drupal\views\Views; - -class ViewSortByDeriver extends ViewDeriverBase { - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions($basePluginDefinition) { - if ($this->entityTypeManager->hasDefinition('view')) { - $viewStorage = $this->entityTypeManager->getStorage('view'); - - foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) { - /** @var \Drupal\views\ViewEntityInterface $view */ - $view = $viewStorage->load($viewId); - if (!$type = $this->getRowResolveType($view, $displayId)) { - continue; - } - - /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */ - $display = $this->getViewDisplay($view, $displayId); - $sorts = array_filter($display->getOption('sorts') ?: [], function ($sort) { - return $sort['exposed']; - }); - $sorts = array_reduce($sorts, function ($carry, $sort) { - $carry[strtoupper($sort['id'])] = [ - 'value' => $sort['id'], - 'description' => $sort['expose']['label'], - ]; - return $carry; - }, []); - - if (!empty($sorts)) { - $id = implode('-', [$viewId, $displayId, 'view']); - $this->derivatives["$viewId-$displayId"] = [ - 'name' => StringHelper::camelCase($id, 'sort', 'by'), - 'values' => $sorts, - ] + $basePluginDefinition; - } - } - } - - return parent::getDerivativeDefinitions($basePluginDefinition); - } - -} diff --git a/src/Plugin/Deriver/Fields/ViewDeriver.php b/src/Plugin/Deriver/Fields/ViewDeriver.php deleted file mode 100644 index fe8577b..0000000 --- a/src/Plugin/Deriver/Fields/ViewDeriver.php +++ /dev/null @@ -1,57 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\Deriver\Fields; - -use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; -use Drupal\graphql_views\Plugin\Deriver\ViewDeriverBase; -use Drupal\views\Views; - -/** - * Derive fields from configured views. - */ -class ViewDeriver extends ViewDeriverBase implements ContainerDeriverInterface { - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions($basePluginDefinition) { - if ($this->entityTypeManager->hasDefinition('view')) { - $viewStorage = $this->entityTypeManager->getStorage('view'); - - foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) { - /** @var \Drupal\views\ViewEntityInterface $view */ - $view = $viewStorage->load($viewId); - if (!$this->getRowResolveType($view, $displayId)) { - continue; - } - - /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */ - $display = $this->getViewDisplay($view, $displayId); - - $id = implode('-', [$viewId, $displayId, 'view']); - $info = $this->getArgumentsInfo($display->getOption('arguments') ?: []); - $arguments = []; - $arguments += $this->getContextualArguments($info, $id); - $arguments += $this->getPagerArguments($display); - $arguments += $this->getSortArguments($display, $id); - $arguments += $this->getFilterArguments($display, $id); - $types = $this->getTypes($info); - - $this->derivatives[$id] = [ - 'id' => $id, - 'name' => $display->getGraphQLQueryName(), - 'type' => $display->getGraphQLResultName(), - 'parents' => $types, - 'arguments' => $arguments, - 'view' => $viewId, - 'display' => $displayId, - 'paged' => $this->isPaged($display), - 'arguments_info' => $info, - ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition; - } - } - - return parent::getDerivativeDefinitions($basePluginDefinition); - } - -} diff --git a/src/Plugin/Deriver/Fields/ViewResultCountDeriver.php b/src/Plugin/Deriver/Fields/ViewResultCountDeriver.php deleted file mode 100644 index fa48865..0000000 --- a/src/Plugin/Deriver/Fields/ViewResultCountDeriver.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\Deriver\Fields; - -use Drupal\graphql_views\Plugin\Deriver\ViewDeriverBase; -use Drupal\views\Views; - -/** - * Derive fields from configured views. - */ -class ViewResultCountDeriver extends ViewDeriverBase { - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions($basePluginDefinition) { - if ($this->entityTypeManager->hasDefinition('view')) { - $viewStorage = $this->entityTypeManager->getStorage('view'); - - foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) { - /** @var \Drupal\views\ViewEntityInterface $view */ - $view = $viewStorage->load($viewId); - /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */ - $display = $this->getViewDisplay($view, $displayId); - if (!$this->isPaged($display)) { - continue; - } - - if (!$this->getRowResolveType($view, $displayId)) { - continue; - } - - $id = implode('-', [$viewId, $displayId, 'result', 'count']); - $this->derivatives[$id] = [ - 'id' => $id, - 'type' => 'Int', - 'parents' => [$display->getGraphQLResultName()], - 'view' => $viewId, - 'display' => $displayId, - ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition; - } - } - - return parent::getDerivativeDefinitions($basePluginDefinition); - } - -} diff --git a/src/Plugin/Deriver/Fields/ViewResultListDeriver.php b/src/Plugin/Deriver/Fields/ViewResultListDeriver.php deleted file mode 100644 index 04e5017..0000000 --- a/src/Plugin/Deriver/Fields/ViewResultListDeriver.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\Deriver\Fields; - -use Drupal\graphql\Utility\StringHelper; -use Drupal\graphql_views\Plugin\Deriver\ViewDeriverBase; -use Drupal\views\Views; - -/** - * Derive fields from configured views. - */ -class ViewResultListDeriver extends ViewDeriverBase { - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions($basePluginDefinition) { - if ($this->entityTypeManager->hasDefinition('view')) { - $viewStorage = $this->entityTypeManager->getStorage('view'); - - foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) { - /** @var \Drupal\views\ViewEntityInterface $view */ - $view = $viewStorage->load($viewId); - if (!$type = $this->getRowResolveType($view, $displayId)) { - continue; - } - - /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */ - $display = $this->getViewDisplay($view, $displayId); - - $id = implode('-', [$viewId, $displayId, 'result', 'list']); - $style = $this->getViewStyle($view, $displayId); - $this->derivatives[$id] = [ - 'id' => $id, - 'type' => StringHelper::listType($type), - 'parents' => [$display->getGraphQLResultName()], - 'view' => $viewId, - 'display' => $displayId, - 'uses_fields' => $style->usesFields(), - ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition; - } - } - - return parent::getDerivativeDefinitions($basePluginDefinition); - } - -} diff --git a/src/Plugin/Deriver/Fields/ViewRowFieldDeriver.php b/src/Plugin/Deriver/Fields/ViewRowFieldDeriver.php deleted file mode 100644 index f42f33f..0000000 --- a/src/Plugin/Deriver/Fields/ViewRowFieldDeriver.php +++ /dev/null @@ -1,54 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\Deriver\Fields; - -use Drupal\graphql_views\Plugin\views\row\GraphQLFieldRow; -use Drupal\graphql_views\Plugin\Deriver\ViewDeriverBase; -use Drupal\views\Views; - -/** - * Derive row fields from configured fieldable views. - */ -class ViewRowFieldDeriver extends ViewDeriverBase { - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions($basePluginDefinition) { - if ($this->entityTypeManager->hasDefinition('view')) { - $viewStorage = $this->entityTypeManager->getStorage('view'); - - foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) { - /** @var \Drupal\views\ViewEntityInterface $view */ - $view = $viewStorage->load($viewId); - /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */ - $display = $this->getViewDisplay($view, $displayId); - $rowPlugin = $display->getPlugin('row'); - - // This deriver only supports our custom field row plugin. - if (!$rowPlugin instanceof GraphQLFieldRow) { - continue; - } - - foreach ($display->getHandlers('field') as $name => $field) { - $id = implode('-', [$viewId, $displayId, 'field', $name]); - $alias = $rowPlugin->getFieldKeyAlias($name); - $type = $rowPlugin->getFieldType($name); - - $this->derivatives[$id] = [ - 'id' => $id, - 'name' => $alias, - 'type' => $type, - 'parents' => [$display->getGraphQLRowName()], - 'view' => $viewId, - 'display' => $displayId, - 'field' => $alias, - ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition; - } - } - } - - return parent::getDerivativeDefinitions($basePluginDefinition); - } - -} diff --git a/src/Plugin/Deriver/InputTypes/ViewContextualFilterInputDeriver.php b/src/Plugin/Deriver/InputTypes/ViewContextualFilterInputDeriver.php deleted file mode 100644 index d7e0825..0000000 --- a/src/Plugin/Deriver/InputTypes/ViewContextualFilterInputDeriver.php +++ /dev/null @@ -1,52 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\Deriver\InputTypes; - -use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; -use Drupal\graphql\Utility\StringHelper; -use Drupal\graphql_views\Plugin\Deriver\ViewDeriverBase; -use Drupal\views\Views; - -/** - * Derive input types for view contextual filters. - */ -class ViewContextualFilterInputDeriver extends ViewDeriverBase implements ContainerDeriverInterface { - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions($basePluginDefinition) { - if ($this->entityTypeManager->hasDefinition('view')) { - $viewStorage = $this->entityTypeManager->getStorage('view'); - - foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) { - /** @var \Drupal\views\ViewEntityInterface $view */ - $view = $viewStorage->load($viewId); - if (!$this->getRowResolveType($view, $displayId)) { - continue; - } - - $display = $this->getViewDisplay($view, $displayId); - $argumentsInfo = $this->getArgumentsInfo($display->getOption('arguments') ?: []); - if (!empty($argumentsInfo)) { - $id = implode('_', [ - $viewId, $displayId, 'view', 'contextual', 'filter', 'input', - ]); - - $this->derivatives[$id] = [ - 'id' => $id, - 'name' => StringHelper::camelCase($viewId, $displayId, 'view', 'contextual', 'filter', 'input'), - 'fields' => array_fill_keys(array_keys($argumentsInfo), [ - 'type' => 'String', - ]), - 'view' => $viewId, - 'display' => $displayId, - ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition; - } - } - } - - return parent::getDerivativeDefinitions($basePluginDefinition); - } - -} diff --git a/src/Plugin/Deriver/InputTypes/ViewFilterInputDeriver.php b/src/Plugin/Deriver/InputTypes/ViewFilterInputDeriver.php deleted file mode 100644 index 78c727f..0000000 --- a/src/Plugin/Deriver/InputTypes/ViewFilterInputDeriver.php +++ /dev/null @@ -1,141 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\Deriver\InputTypes; - -use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; -use Drupal\graphql\Utility\StringHelper; -use Drupal\graphql_views\Plugin\Deriver\ViewDeriverBase; -use Drupal\views\Views; - -/** - * Derive fields from configured views. - */ -class ViewFilterInputDeriver extends ViewDeriverBase implements ContainerDeriverInterface { - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions($basePluginDefinition) { - if ($this->entityTypeManager->hasDefinition('view')) { - $viewStorage = $this->entityTypeManager->getStorage('view'); - - foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) { - /** @var \Drupal\views\ViewEntityInterface $view */ - $view = $viewStorage->load($viewId); - if (!$this->getRowResolveType($view, $displayId)) { - continue; - } - - /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */ - $display = $this->getViewDisplay($view, $displayId); - $id = implode('_', [$viewId, $displayId, 'view', 'filter', 'input']); - - // Re-key filters by filter identifier. - $filters = array_reduce(array_filter($display->getOption('filters') ?: [], function($filter) { - return array_key_exists('exposed', $filter) && $filter['exposed']; - }), function($carry, $current) { - return $carry + [ - $current['expose']['identifier'] => $current, - ]; - }, []); - - // If there are no exposed filters, don't create the derivative. - if (empty($filters)) { - continue; - } - - $fields = array_map(function($filter) use ($basePluginDefinition) { - if ($this->isGenericInputFilter($filter)) { - return $this->createGenericInputFilterDefinition($filter, $basePluginDefinition); - } - - return [ - 'type' => $filter['expose']['multiple'] ? StringHelper::listType('String') : 'String', - ]; - }, $filters); - - $this->derivatives[$id] = [ - 'id' => $id, - 'name' => $display->getGraphQLFilterInputName(), - 'fields' => $fields, - 'view' => $viewId, - 'display' => $displayId, - ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition; - } - - } - return parent::getDerivativeDefinitions($basePluginDefinition); - } - - /** - * Checks if a filter definition is a generic input filter. - * - * @param mixed $filter - * $filter['value'] = []; - * $filter['value'] = [ - * "text", - * "test" - * ]; - * $filter['value'] = [ - * 'distance' => 10, - * 'distance2' => 30, - * ... - * ]; - * @return bool - */ - public function isGenericInputFilter($filter) { - if (!is_array($filter['value']) || count($filter['value']) == 0) { - return false; - } - - $firstKey = array_keys($filter['value'])[0]; - return is_string($firstKey); - } - - /** - * Creates a definition for a generic input filter. - * - * @param mixed $filter - * $filter['value'] = []; - * $filter['value'] = [ - * "text", - * "test" - * ]; - * $filter['value'] = [ - * 'distance' => 10, - * 'distance2' => 30, - * ... - * ]; - * @param mixed $basePluginDefinition - * @return array - */ - public function createGenericInputFilterDefinition($filter, $basePluginDefinition) { - $filterId = $filter['expose']['identifier']; - - $id = implode('_', [ - $filter['expose']['multiple'] ? $filterId : $filterId . '_multi', - 'view', - 'filter', - 'input', - ]); - - $fields = []; - foreach ($filter['value'] as $fieldKey => $fieldDefaultValue) { - $fields[$fieldKey] = [ - 'type' => 'String', - ]; - } - - $genericInputFilter = [ - 'id' => $id, - 'name' => StringHelper::camelCase($id), - 'fields' => $fields, - ] + $basePluginDefinition; - - $this->derivatives[$id] = $genericInputFilter; - - return [ - 'type' => $filter['expose']['multiple'] ? StringHelper::listType($genericInputFilter['name']) : $genericInputFilter['name'], - ]; - } -} diff --git a/src/Plugin/Deriver/Types/ViewResultTypeDeriver.php b/src/Plugin/Deriver/Types/ViewResultTypeDeriver.php deleted file mode 100644 index 2d4c71e..0000000 --- a/src/Plugin/Deriver/Types/ViewResultTypeDeriver.php +++ /dev/null @@ -1,43 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\Deriver\Types; - -use Drupal\graphql_views\Plugin\Deriver\ViewDeriverBase; -use Drupal\views\Views; - -/** - * Derive fields from configured views. - */ -class ViewResultTypeDeriver extends ViewDeriverBase { - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions($basePluginDefinition) { - if ($this->entityTypeManager->hasDefinition('view')) { - $viewStorage = $this->entityTypeManager->getStorage('view'); - - foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) { - /** @var \Drupal\views\ViewEntityInterface $view */ - $view = $viewStorage->load($viewId); - if (!$this->getRowResolveType($view, $displayId)) { - continue; - } - - /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */ - $display = $this->getViewDisplay($view, $displayId); - - $id = implode('-', [$viewId, $displayId, 'result']); - $this->derivatives[$id] = [ - 'id' => $id, - 'name' => $display->getGraphQLResultName(), - 'view' => $viewId, - 'display' => $displayId, - ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition; - } - } - - return parent::getDerivativeDefinitions($basePluginDefinition); - } - -} diff --git a/src/Plugin/Deriver/Types/ViewRowTypeDeriver.php b/src/Plugin/Deriver/Types/ViewRowTypeDeriver.php deleted file mode 100644 index 0a69781..0000000 --- a/src/Plugin/Deriver/Types/ViewRowTypeDeriver.php +++ /dev/null @@ -1,49 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\Deriver\Types; - -use Drupal\graphql_views\Plugin\Deriver\ViewDeriverBase; -use Drupal\views\Views; - -/** - * Derive row types from configured fieldable views. - */ -class ViewRowTypeDeriver extends ViewDeriverBase { - - /** - * {@inheritdoc} - */ - public function getDerivativeDefinitions($basePluginDefinition) { - if ($this->entityTypeManager->hasDefinition('view')) { - $viewStorage = $this->entityTypeManager->getStorage('view'); - - foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) { - /** @var \Drupal\views\ViewEntityInterface $view */ - $view = $viewStorage->load($viewId); - if (!$this->getRowResolveType($view, $displayId)) { - continue; - } - - $style = $this->getViewStyle($view, $displayId); - // This deriver only supports style plugins that use fields. - if (!$style->usesFields()) { - continue; - } - - /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */ - $display = $this->getViewDisplay($view, $displayId); - - $id = implode('-', [$viewId, $displayId, 'row']); - $this->derivatives[$id] = [ - 'id' => $id, - 'name' => $display->getGraphQLRowName(), - 'view' => $viewId, - 'display' => $displayId, - ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition; - } - } - - return parent::getDerivativeDefinitions($basePluginDefinition); - } - -} diff --git a/src/Plugin/Deriver/ViewDeriverBase.php b/src/Plugin/Deriver/ViewDeriverBase.php deleted file mode 100644 index 685088d..0000000 --- a/src/Plugin/Deriver/ViewDeriverBase.php +++ /dev/null @@ -1,113 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\Deriver; - -use Drupal\Component\Plugin\Derivative\DeriverBase; -use Drupal\Component\Plugin\PluginManagerInterface; -use Drupal\Component\Utility\NestedArray; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; -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; - -/** - * Base class for graphql view derivers. - */ -abstract class ViewDeriverBase extends DeriverBase implements ContainerDeriverInterface { - use ViewDeriverHelperTrait { - getRowResolveType as private traitGetRowResolveType; - } - /** - * The entity type manager. - * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface - */ - protected $entityTypeManager; - - /** - * The interface plugin manager to search for return type candidates. - * - * @var \Drupal\Component\Plugin\PluginManagerInterface - */ - protected $interfacePluginManager; - - /** - * An key value pair of data tables and the entities they belong to. - * - * @var string[] - */ - protected $dataTables; - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, $basePluginId) { - return new static( - $container->get('entity_type.manager'), - $container->get('plugin.manager.graphql.interface') - ); - } - - /** - * Creates a ViewDeriver object. - * - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager - * An entity type manager instance. - * @param \Drupal\Component\Plugin\PluginManagerInterface $interfacePluginManager - * The plugin manager for graphql interfaces. - */ - public function __construct( - EntityTypeManagerInterface $entityTypeManager, - PluginManagerInterface $interfacePluginManager - ) { - $this->interfacePluginManager = $interfacePluginManager; - $this->entityTypeManager = $entityTypeManager; - } - - /** - * Retrieves the entity type id of an entity by its base or data table. - * - * @param string $table - * The base or data table of an entity. - * - * @return string - * The id of the entity type that the given base table belongs to. - */ - protected function getEntityTypeByTable($table) { - if (!isset($this->dataTables)) { - $this->dataTables = []; - - foreach ($this->entityTypeManager->getDefinitions() as $entityTypeId => $entityType) { - if ($dataTable = $entityType->getDataTable()) { - $this->dataTables[$dataTable] = $entityType->id(); - } - if ($baseTable = $entityType->getBaseTable()) { - $this->dataTables[$baseTable] = $entityType->id(); - } - } - } - - return !empty($this->dataTables[$table]) ? $this->dataTables[$table] : NULL; - } - - /** - * Retrieves the type the view's rows resolve to. - * - * @param \Drupal\views\ViewEntityInterface $view - * The view entity. - * @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) { - return $this->traitGetRowResolveType($view, $displayId, $this->interfacePluginManager); - } - -} diff --git a/src/Plugin/GraphQL/DataProducer/Views.php b/src/Plugin/GraphQL/DataProducer/Views.php new file mode 100644 index 0000000..100fd9b --- /dev/null +++ b/src/Plugin/GraphQL/DataProducer/Views.php @@ -0,0 +1,127 @@ +<?php + +namespace Drupal\graphql_views\Plugin\GraphQL\DataProducer; + +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase; +use Drupal\views\Plugin\views\display\DisplayPluginInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * GraphQL data producer for views. + * + * @DataProducer( + * id = "views", + * name = @Translation("Views"), + * description = @Translation("Views."), + * produces = @ContextDefinition("entity", + * label = @Translation("Entity") + * ), + * consumes = { + * "view_id" = @ContextDefinition("string", + * label = @Translation("View ID") + * ), + * "display_id" = @ContextDefinition("string", + * label = @Translation("Display ID") + * ), + * "offset" = @ContextDefinition("integer", + * label = @Translation("Offset"), + * required = FALSE + * ), + * "page_size" = @ContextDefinition("integer", + * label = @Translation("Page size"), + * required = FALSE + * ), + * "page" = @ContextDefinition("integer", + * label = @Translation("Current page"), + * required = FALSE + * ) + * } + * ) + */ +class Views extends DataProducerPluginBase implements ContainerFactoryPluginInterface { + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static($configuration, $plugin_id, $plugin_definition, $container->get('entity_type.manager')); + } + + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entityTypeManager) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->entityTypeManager = $entityTypeManager; + } + + /** + * Resolve the data. + * + * @param string $view_id + * The view ID. + * @param string $display_id + * The display ID. + * @param int|null $offset + * Offset of the query. + * @param int|null $page_size + * Number of items on page. + * @param int|null $page + * Number of page. + * + * @return array|null + * List of entities. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + public function resolve(string $view_id, string $display_id, int $offset = NULL, int $page_size = NULL, int $page = NULL) { + + /** @var \Drupal\views\Entity\View $view */ + $view = \Drupal::entityTypeManager()->getStorage('view')->load($view_id); + + $executable = $view->getExecutable(); + $executable->setDisplay($display_id); + + // Set paging parameters. + if ($this->isPaged($executable->getDisplay()) && $page_size && $page) { + $executable->setItemsPerPage($page_size); + $executable->setCurrentPage($page); + } + + if ($offset) { + $executable->setOffset($offset); + } + + $executable->preExecute(); + $executable->execute(); + return $executable->render($display_id); + } + + /** + * 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', + ]); + } + +} diff --git a/src/Plugin/GraphQL/Enums/ViewSortBy.php b/src/Plugin/GraphQL/Enums/ViewSortBy.php deleted file mode 100644 index 9a3b730..0000000 --- a/src/Plugin/GraphQL/Enums/ViewSortBy.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\Enums; - -use Drupal\graphql\Plugin\GraphQL\Enums\EnumPluginBase; - -/** - * @GraphQLEnum( - * id = "view_sort_by", - * provider = "views", - * deriver = "Drupal\graphql_views\Plugin\Deriver\Enums\ViewSortByDeriver" - * ) - */ -class ViewSortBy extends EnumPluginBase { - -} diff --git a/src/Plugin/GraphQL/Enums/ViewSortDirection.php b/src/Plugin/GraphQL/Enums/ViewSortDirection.php deleted file mode 100644 index 54853a3..0000000 --- a/src/Plugin/GraphQL/Enums/ViewSortDirection.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\Enums; - -use Drupal\graphql\Plugin\GraphQL\Enums\EnumPluginBase; - -/** - * @GraphQLEnum( - * id = "view_sort_direction", - * name = "ViewSortDirection", - * provider = "views", - * ) - */ -class ViewSortDirection extends EnumPluginBase { - - /** - * {@inheritdoc} - */ - protected function buildEnumValues($definition) { - return [ - 'ASC' => [ - 'value' => 'ASC', - 'description' => 'Sort in ascending order.', - ], - 'DESC' => [ - 'value' => 'DESC', - 'description' => 'Sort in descending order.', - ], - ]; - } - -} diff --git a/src/Plugin/GraphQL/Fields/Entity/Fields/View/ViewDerivative.php b/src/Plugin/GraphQL/Fields/Entity/Fields/View/ViewDerivative.php deleted file mode 100644 index d582f5d..0000000 --- a/src/Plugin/GraphQL/Fields/Entity/Fields/View/ViewDerivative.php +++ /dev/null @@ -1,141 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\Fields\Entity\Fields\View; - -use Drupal\graphql\GraphQL\Execution\ResolveContext; -use Drupal\graphql_views\Plugin\GraphQL\Fields\View; -use Drupal\graphql_views\ViewDeriverHelperTrait; -use Drupal\views\Entity\View as EntityView; -use Drupal\views\Plugin\views\display\DisplayPluginInterface; -use GraphQL\Type\Definition\ResolveInfo; - -/** - * Retrieve the views field derivative. - * - * @GraphQLField( - * id = "view_derivative", - * secure = true, - * name = "viewDerivative", - * type = "ViewResult", - * field_types = {"viewsreference"}, - * provider = "viewsreference", - * arguments={ - * "filter" = { - * "optional" = true, - * "type" = "ViewsFilterInput" - * }, - * "offset" = { - * "optional" = true, - * "type" = "Int" - * }, - * "page" = { - * "optional" = true, - * "type" = "Int" - * }, - * "pageSize" = { - * "optional" = true, - * "type" = "Int" - * }, - * "sortBy" = { - * "optional" = true, - * "type" = "ViewsSortByInput" - * }, - * "sortDirection" = { - * "optional" = true, - * "type" = "ViewSortDirection", - * "default" = "asc" - * }, - * "contextualFilter" = { - * "optional" = true, - * "type" = "ViewsContextualFilterInput" - * } - * }, - * deriver = "Drupal\graphql_core\Plugin\Deriver\Fields\EntityFieldPropertyDeriver" - * ) - */ -class ViewDerivative extends View { - use ViewDeriverHelperTrait; - - /** - * {@inheritdoc} - */ - public function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) { - $values = $value->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 deleted file mode 100644 index 4b4aa25..0000000 --- a/src/Plugin/GraphQL/Fields/View.php +++ /dev/null @@ -1,197 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\Fields; - -use Drupal\Core\DependencyInjection\DependencySerializationTrait; -use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\graphql\GraphQL\Execution\ResolveContext; -use Drupal\graphql\Plugin\GraphQL\Fields\FieldPluginBase; -use GraphQL\Type\Definition\ResolveInfo; -use Symfony\Component\DependencyInjection\ContainerInterface; - -/** - * Expose views as root fields. - * - * @GraphQLField( - * id = "view", - * secure = true, - * parents = {"Root"}, - * provider = "views", - * deriver = "Drupal\graphql_views\Plugin\Deriver\Fields\ViewDeriver" - * ) - */ -class View extends FieldPluginBase implements ContainerFactoryPluginInterface { - use DependencySerializationTrait; - - /** - * The entity type manager. - * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface - */ - protected $entityTypeManager; - - /** - * {@inheritdoc} - */ - public function __construct( - array $configuration, - $pluginId, - $pluginDefinition, - EntityTypeManagerInterface $entityTypeManager - ) { - $this->entityTypeManager = $entityTypeManager; - parent::__construct($configuration, $pluginId, $pluginDefinition); - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) { - return new static( - $configuration, - $pluginId, - $pluginDefinition, - $container->get('entity_type.manager') - ); - } - - /** - * {@inheritdoc} - */ - public function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) { - $storage = $this->entityTypeManager->getStorage('view'); - $definition = $this->getPluginDefinition(); - - /** @var \Drupal\views\Entity\View $view */ - if ($view = $storage->load($definition['view'])) { - $executable = $view->getExecutable(); - $executable->setDisplay($definition['display']); - - // Set view contextual filters. - /* @see \Drupal\graphql_views\ViewDeriverHelperTrait::getArgumentsInfo() */ - if (!empty($definition['arguments_info'])) { - $arguments = $this->extractContextualFilters($value, $args); - $executable->setArguments($arguments); - } - - $filters = $executable->getDisplay()->getOption('filters');; - $input = $this->extractExposedInput($value, $args, $filters); - $executable->setExposedInput($input); - - // This is a workaround for the Taxonomy Term filter which requires a full - // exposed form to be sent OR the display being an attachment to just - // accept input values. - $executable->is_attachment = TRUE; - $executable->exposed_raw_input = $input; - - if (!empty($definition['paged'])) { - // Set paging parameters. - $executable->setItemsPerPage($args['pageSize']); - $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']) { - $cache->setCacheContexts( - array_filter($cache->getCacheContexts(), function ($context) { - // Don't emit the url cache contexts. - return $context !== 'url' && strpos($context, 'url.') !== 0; - }) - ); - } - yield $result; - } - } - - /** - * {@inheritdoc} - */ - protected function getCacheDependencies(array $result, $value, array $args, ResolveContext $context, ResolveInfo $info) { - return array_map(function ($item) { - return $item['cache']; - }, $result); - } - - /** - * Retrieves the contextual filter argument from the parent value or args. - * - * @param $value - * The resolved parent value. - * @param $args - * The arguments provided to the field. - * - * @return array - * An array of arguments containing the contextual filter value from the - * parent or provided args if any. - */ - protected function extractContextualFilters($value, $args) { - $definition = $this->getPluginDefinition(); - $arguments = []; - - foreach ($definition['arguments_info'] as $argumentId => $argumentInfo) { - if (isset($args['contextualFilter'][$argumentId])) { - $arguments[$argumentInfo['index']] = $args['contextualFilter'][$argumentId]; - } - elseif ( - $value instanceof EntityInterface && - $value->getEntityTypeId() === $argumentInfo['entity_type'] && - (empty($argumentInfo['bundles']) || - in_array($value->bundle(), $argumentInfo['bundles'], TRUE)) - ) { - $arguments[$argumentInfo['index']] = $value->id(); - } - else { - $arguments[$argumentInfo['index']] = NULL; - } - } - - return $arguments; - } - - /** - * Retrieves sort and filter arguments from the provided field args. - * - * @param $value - * The resolved parent value. - * @param $args - * The array of arguments provided to the field. - * @param $filters - * The available filters for the configured view. - * - * @return array - * The array of sort and filter arguments to execute the view with. - */ - protected function extractExposedInput($value, $args, $filters) { - // Prepare arguments for use as exposed form input. - $input = array_filter([ - // Sorting arguments. - 'sort_by' => isset($args['sortBy']) ? $args['sortBy'] : NULL, - 'sort_order' => isset($args['sortDirection']) ? $args['sortDirection'] : NULL, - ]); - - // If some filters are missing from the input, set them to an empty string - // explicitly. Otherwise views module generates "Undefined index" notice. - foreach ($filters as $filterKey => $filterRow) { - if (!isset($filterRow['expose']['identifier'])) { - continue; - } - - $inputKey = $filterRow['expose']['identifier']; - if (!isset($args['filter'][$inputKey])) { - $input[$inputKey] = $filterRow['value']; - } else { - $input[$inputKey] = $args['filter'][$inputKey]; - } - } - - return $input; - } - -} diff --git a/src/Plugin/GraphQL/Fields/ViewResultCount.php b/src/Plugin/GraphQL/Fields/ViewResultCount.php deleted file mode 100644 index a54e7cb..0000000 --- a/src/Plugin/GraphQL/Fields/ViewResultCount.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\Fields; - -use Drupal\graphql\GraphQL\Execution\ResolveContext; -use Drupal\graphql\Plugin\GraphQL\Fields\FieldPluginBase; -use Drupal\views\ViewExecutable; -use GraphQL\Type\Definition\ResolveInfo; - -/** - * Expose result count of a view. - * - * @GraphQLField( - * id = "view_result_count", - * name = "count", - * secure = true, - * type = "Int!", - * provider = "views", - * deriver = "Drupal\graphql_views\Plugin\Deriver\Fields\ViewResultCountDeriver" - * ) - */ -class ViewResultCount extends FieldPluginBase { - - /** - * {@inheritdoc} - */ - public function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) { - if (isset($value['view']) && $value['view'] instanceof ViewExecutable) { - yield intval($value['view']->total_rows); - } - } - -} diff --git a/src/Plugin/GraphQL/Fields/ViewResultList.php b/src/Plugin/GraphQL/Fields/ViewResultList.php deleted file mode 100644 index 1026b74..0000000 --- a/src/Plugin/GraphQL/Fields/ViewResultList.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\Fields; - -use Drupal\graphql\GraphQL\Execution\ResolveContext; -use Drupal\graphql\Plugin\GraphQL\Fields\FieldPluginBase; -use GraphQL\Type\Definition\ResolveInfo; - -/** - * Expose results of a view. - * - * @GraphQLField( - * id = "view_result", - * name = "results", - * secure = true, - * provider = "views", - * deriver = "Drupal\graphql_views\Plugin\Deriver\Fields\ViewResultListDeriver" - * ) - */ -class ViewResultList extends FieldPluginBase { - - /** - * {@inheritdoc} - */ - protected function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) { - if (isset($value['rows'])) { - foreach ($value['rows'] as $row) { - yield $row; - } - } - } - -} diff --git a/src/Plugin/GraphQL/Fields/ViewRowField.php b/src/Plugin/GraphQL/Fields/ViewRowField.php deleted file mode 100644 index bfaec7e..0000000 --- a/src/Plugin/GraphQL/Fields/ViewRowField.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\Fields; - -use Drupal\graphql\GraphQL\Execution\ResolveContext; -use Drupal\graphql\Plugin\GraphQL\Fields\FieldPluginBase; -use GraphQL\Type\Definition\ResolveInfo; - -/** - * Expose view row fields for configured fieldable views. - * - * @GraphQLField( - * id = "view_row_field", - * secure = true, - * provider = "views", - * deriver = "Drupal\graphql_views\Plugin\Deriver\Fields\ViewRowFieldDeriver" - * ) - */ -class ViewRowField extends FieldPluginBase { - - /** - * {@inheritdoc} - */ - public function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) { - $definition = $this->getPluginDefinition(); - if (isset($value[$definition['field']])) { - yield $value[$definition['field']]; - } - } - -} diff --git a/src/Plugin/GraphQL/InputTypes/ViewContextualFilterInput.php b/src/Plugin/GraphQL/InputTypes/ViewContextualFilterInput.php deleted file mode 100644 index c208941..0000000 --- a/src/Plugin/GraphQL/InputTypes/ViewContextualFilterInput.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\InputTypes; - -use Drupal\graphql\Plugin\GraphQL\InputTypes\InputTypePluginBase; - -/** - * Input types for view contextual filters. - * - * @GraphQLInputType( - * id = "view_contextual_filter_input", - * provider = "views", - * deriver = "Drupal\graphql_views\Plugin\Deriver\InputTypes\ViewContextualFilterInputDeriver" - * ) - */ -class ViewContextualFilterInput extends InputTypePluginBase { - -} diff --git a/src/Plugin/GraphQL/InputTypes/ViewFilterInput.php b/src/Plugin/GraphQL/InputTypes/ViewFilterInput.php deleted file mode 100644 index aef98bd..0000000 --- a/src/Plugin/GraphQL/InputTypes/ViewFilterInput.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\InputTypes; - -use Drupal\graphql\Plugin\GraphQL\InputTypes\InputTypePluginBase; - -/** - * Creates input types for entity mutations. - * - * @GraphQLInputType( - * id = "view_filter_input", - * provider = "views", - * deriver = "Drupal\graphql_views\Plugin\Deriver\InputTypes\ViewFilterInputDeriver" - * ) - */ -class ViewFilterInput extends InputTypePluginBase { - -} diff --git a/src/Plugin/GraphQL/Scalars/TypedData/ViewsContextualFilterInput.php b/src/Plugin/GraphQL/Scalars/TypedData/ViewsContextualFilterInput.php deleted file mode 100644 index 9f2c950..0000000 --- a/src/Plugin/GraphQL/Scalars/TypedData/ViewsContextualFilterInput.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\Scalars\TypedData; - -use Drupal\graphql\Plugin\GraphQL\Scalars\ScalarPluginBase; -use GraphQL\Utils\AST; - -/** - * Input types for view contextual filters. - * - * @GraphQLScalar( - * id = "views_contextual_filter_input", - * name = "ViewsContextualFilterInput", - * type = "ViewsContextualFilterInput", - * provider = "views" - * ) - */ -class ViewsContextualFilterInput extends ScalarPluginBase { - // @TODO: Untyped input is there because there is no option to create a InputType union. See discussion: https://github.com/graphql/graphql-js/issues/207 and https://github.com/facebook/graphql/issues/488. - - /** - * {@inheritdoc} - */ - public static function parseLiteral($node) { - return AST::valueFromASTUntyped($node); - } - -} diff --git a/src/Plugin/GraphQL/Scalars/TypedData/ViewsFilterInput.php b/src/Plugin/GraphQL/Scalars/TypedData/ViewsFilterInput.php deleted file mode 100644 index 5af9a86..0000000 --- a/src/Plugin/GraphQL/Scalars/TypedData/ViewsFilterInput.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\Scalars\TypedData; - -use Drupal\graphql\Plugin\GraphQL\Scalars\ScalarPluginBase; -use GraphQL\Utils\AST; - -/** - * Input types for view contextual filters. - * - * @GraphQLScalar( - * id = "views_filter_input", - * name = "ViewsFilterInput", - * type = "ViewsFilterInput", - * provider = "views" - * ) - */ -class ViewsFilterInput extends ScalarPluginBase { - // @TODO: Untyped input is there because there is no option to create a InputType union. See discussion: https://github.com/graphql/graphql-js/issues/207 and https://github.com/facebook/graphql/issues/488. - - /** - * {@inheritdoc} - */ - public static function parseLiteral($node) { - return AST::valueFromASTUntyped($node); - } - -} diff --git a/src/Plugin/GraphQL/Scalars/TypedData/ViewsSortByInput.php b/src/Plugin/GraphQL/Scalars/TypedData/ViewsSortByInput.php deleted file mode 100644 index 8611c69..0000000 --- a/src/Plugin/GraphQL/Scalars/TypedData/ViewsSortByInput.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\Scalars\TypedData; - -use Drupal\graphql\Plugin\GraphQL\Scalars\ScalarPluginBase; -use GraphQL\Utils\AST; - -/** - * Input types for view contextual filters. - * - * @GraphQLScalar( - * id = "views_sort_by_input", - * name = "ViewsSortByInput", - * type = "ViewsSortByInput", - * provider = "views" - * ) - */ -class ViewsSortByInput extends ScalarPluginBase { - // @TODO: Untyped input is there because there is no option to create a InputType union. See discussion: https://github.com/graphql/graphql-js/issues/207 and https://github.com/facebook/graphql/issues/488. - - /** - * {@inheritdoc} - */ - public static function parseLiteral($node) { - return AST::valueFromASTUntyped($node); - } - -} diff --git a/src/Plugin/GraphQL/Types/ViewResultType.php b/src/Plugin/GraphQL/Types/ViewResultType.php deleted file mode 100644 index 8510989..0000000 --- a/src/Plugin/GraphQL/Types/ViewResultType.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\Types; - -use Drupal\graphql\GraphQL\Execution\ResolveContext; -use Drupal\graphql\Plugin\GraphQL\Types\TypePluginBase; -use GraphQL\Type\Definition\ResolveInfo; - -/** - * Expose views as root fields. - * - * @GraphQLType( - * id = "view_result_type", - * provider = "views", - * unions = {"ViewResult"}, - * deriver = "Drupal\graphql_views\Plugin\Deriver\Types\ViewResultTypeDeriver" - * ) - */ -class ViewResultType extends TypePluginBase { - - /** - * {@inheritdoc} - */ - public function applies($object, ResolveContext $context, ResolveInfo $info) { - if (isset($object['view'])) { - /* @var \Drupal\views\Entity\View $view */ - $view = $object['view']; - if ($this->pluginDefinition['view'] === $view->id() && $this->pluginDefinition['display'] == $view->current_display) { - return TRUE; - } - } - - return parent::applies($object, $context, $info); - } - -} diff --git a/src/Plugin/GraphQL/Types/ViewRowType.php b/src/Plugin/GraphQL/Types/ViewRowType.php deleted file mode 100644 index 7c54185..0000000 --- a/src/Plugin/GraphQL/Types/ViewRowType.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\Types; - -use Drupal\graphql\Plugin\GraphQL\Types\TypePluginBase; - -/** - * Expose types for fieldable views' rows. - * - * @GraphQLType( - * id = "view_row_type", - * provider = "views", - * deriver = "Drupal\graphql_views\Plugin\Deriver\Types\ViewRowTypeDeriver" - * ) - */ -class ViewRowType extends TypePluginBase { - -} diff --git a/src/Plugin/GraphQL/UnionTypes/ViewResult.php b/src/Plugin/GraphQL/UnionTypes/ViewResult.php deleted file mode 100644 index 85ee20f..0000000 --- a/src/Plugin/GraphQL/UnionTypes/ViewResult.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\GraphQL\UnionTypes; - -use Drupal\graphql\Plugin\GraphQL\Unions\UnionTypePluginBase; - -/** - * @GraphQLUnionType( - * id = "view_result", - * name = "ViewResult", - * provider = "views", - * description = @Translation("Common view interface containing generic view properties.") - * ) - */ -class ViewResult extends UnionTypePluginBase { - -} diff --git a/src/Plugin/views/display/GraphQL.php b/src/Plugin/views/display/GraphQL.php index 1135191..664c05e 100644 --- a/src/Plugin/views/display/GraphQL.php +++ b/src/Plugin/views/display/GraphQL.php @@ -1,16 +1,8 @@ <?php -/** - * @file - * Contains \Drupal\graphql\Plugin\views\display - */ - namespace Drupal\graphql_views\Plugin\views\display; -use Drupal\Core\Cache\CacheableMetadata; -use Drupal\graphql\Utility\StringHelper; use Drupal\views\Plugin\views\display\DisplayPluginBase; -use Drupal\Core\Form\FormStateInterface; /** * Provides a display plugin for GraphQL views. @@ -25,28 +17,29 @@ * ) */ class GraphQL extends DisplayPluginBase { + /** - * Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::$usesAJAX. + * {@inheritdoc} */ protected $usesAJAX = FALSE; /** - * Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::$usesPager. + * {@inheritdoc} */ protected $usesPager = FALSE; /** - * Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::$usesMore. + * {@inheritdoc} */ protected $usesMore = FALSE; /** - * Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::$usesAreas. + * {@inheritdoc} */ protected $usesAreas = FALSE; /** - * Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::$usesOptions. + * {@inheritdoc} */ protected $usesOptions = TRUE; @@ -93,7 +86,8 @@ protected function defineOptions() { $options['defaults']['default']['exposed_form'] = FALSE; $options['defaults']['default']['row'] = FALSE; - // Remove css/exposed form settings, as they are not used for the data display. + // Remove css/exposed form settings, as they are not used for the data + // display. unset($options['exposed_block']); unset($options['css_class']); @@ -101,82 +95,6 @@ protected function defineOptions() { return $options; } - /** - * Get the user defined query name or the default one. - * - * @return string - * Query name. - */ - public function getGraphQLQueryName() { - return $this->getGraphQLName(); - } - - /** - * Gets the result name. - * - * @return string - * Result name. - */ - public function getGraphQLResultName() { - return $this->getGraphQLName('result', TRUE); - } - - /** - * Gets the row name. - * - * @return string - * Row name. - */ - public function getGraphQLRowName() { - return $this->getGraphQLName('row', TRUE); - } - - /** - * Gets the filter input name.. - * - * @return string - * Result name. - */ - public function getGraphQLFilterInputName() { - return $this->getGraphQLName('filter_input', TRUE); - } - - /** - * Gets the contextual filter input name. - * - * @return string - * Result name. - */ - public function getGraphQLContextualFilterInputName() { - return $this->getGraphQLName('contextual_filter_input', TRUE); - } - - /** - * Returns the formatted name. - * - * @param string|null $suffix - * Id suffix, eg. row, result. - * @param bool $type - * Whether to use camel- or snake case. Uses camel case if TRUE. Defaults to - * FALSE. - * - * @return string The id. - * The id. - */ - public function getGraphQLName($suffix = NULL, $type = FALSE) { - $queryName = strip_tags($this->getOption('graphql_query_name')); - - if (empty($queryName)) { - $viewId = $this->view->id(); - $displayId = $this->display['id']; - $parts = [$viewId, $displayId, 'view', $suffix]; - return $type ? call_user_func_array([StringHelper::class, 'camelCase'], $parts) : call_user_func_array([StringHelper::class, 'propCase'], $parts); - } - - $parts = array_filter([$queryName, $suffix]); - return $type ? call_user_func_array([StringHelper::class, 'camelCase'], $parts) : call_user_func_array([StringHelper::class, 'propCase'], $parts); - } - /** * {@inheritdoc} */ @@ -200,43 +118,6 @@ public function optionsSummary(&$categories, &$options) { '#weight' => -10, ], ]; - - $options['graphql_query_name'] = [ - 'category' => 'graphql', - 'title' => $this->t('Query name'), - 'value' => views_ui_truncate($this->getGraphQLQueryName(), 24), - ]; - } - - /** - * {@inheritdoc} - */ - public function buildOptionsForm(&$form, FormStateInterface $form_state) { - parent::buildOptionsForm($form, $form_state); - - switch ($form_state->get('section')) { - case 'graphql_query_name': - $form['#title'] .= $this->t('Query name'); - $form['graphql_query_name'] = [ - '#type' => 'textfield', - '#description' => $this->t('This will be the graphQL query name.'), - '#default_value' => $this->getGraphQLQueryName(), - ]; - break; - } - } - - /** - * {@inheritdoc} - */ - public function submitOptionsForm(&$form, FormStateInterface $form_state) { - parent::submitOptionsForm($form, $form_state); - $section = $form_state->get('section'); - switch ($section) { - case 'graphql_query_name': - $this->setOption($section, $form_state->getValue($section)); - break; - } } /** @@ -256,10 +137,7 @@ public function render() { // cache render array so we have to transform it back afterwards. $this->applyDisplayCacheabilityMetadata($this->view->element); - return [ - 'view' => $this->view, - 'rows' => $rows, - 'cache' => CacheableMetadata::createFromRenderArray($this->view->element), - ]; + return $rows; } + } diff --git a/src/Plugin/views/exposed_form/GraphQL.php b/src/Plugin/views/exposed_form/GraphQL.php index 0865bb2..ac0f631 100644 --- a/src/Plugin/views/exposed_form/GraphQL.php +++ b/src/Plugin/views/exposed_form/GraphQL.php @@ -26,4 +26,5 @@ public function renderExposedForm($block = FALSE) { return NULL; } + } diff --git a/src/Plugin/views/row/GraphQLEntityRow.php b/src/Plugin/views/row/GraphQLEntityRow.php index 82b4fbb..1fab4f5 100644 --- a/src/Plugin/views/row/GraphQLEntityRow.php +++ b/src/Plugin/views/row/GraphQLEntityRow.php @@ -4,7 +4,6 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityRepositoryInterface; -use Drupal\Core\Entity\EntityTypeBundleInfo; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\Plugin\DataType\EntityAdapter; use Drupal\Core\Language\LanguageManagerInterface; @@ -35,18 +34,11 @@ class GraphQLEntityRow extends RowPluginBase { protected $usesOptions = FALSE; /** - * Contains the entity type of this row plugin instance. - * - * @var \Drupal\Core\Entity\EntityTypeInterface - */ - protected $entityType; - - /** - * The entity type bundle info. + * The language manager. * - * @var \Drupal\Core\Entity\EntityTypeBundleInfo + * @var \Drupal\Core\Language\LanguageManagerInterface */ - protected $entityTypeBundleInfo; + protected $languageManager; /** * The entity type manager. @@ -63,42 +55,44 @@ class GraphQLEntityRow extends RowPluginBase { protected $entityRepository; /** - * The language manager. - * - * @var \Drupal\Core\Language\LanguageManagerInterface + * {@inheritdoc} */ - protected $languageManager; + public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) { + $plugin = parent::create($container, $configuration, $pluginId, $pluginDefinition); + $plugin->setLanguageManager($container->get('language_manager')); + $plugin->setEntityTypeManager($container->get('entity_type.manager')); + $plugin->setEntityRepository($container->get('entity.repository')); + return $plugin; + } /** - * {@inheritdoc} + * Set the language manager. * - * @param \Drupal\Core\Entity\EntityTypeBundleInfo $entityTypeBundleInfo - * The entity type manager. * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager * The language manager. */ - public function __construct(array $configuration, $pluginId, $pluginDefinition, EntityTypeBundleInfo $entityTypeBundleInfo, LanguageManagerInterface $languageManager, EntityTypeManagerInterface $entityTypeManager, EntityRepositoryInterface $entityRepository) { - parent::__construct($configuration, $pluginId, $pluginDefinition); - - $this->entityTypeBundleInfo = $entityTypeBundleInfo; + protected function setLanguageManager(LanguageManagerInterface $languageManager) { $this->languageManager = $languageManager; + } + + /** + * Set the entity type manager. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * The entity type manager. + */ + protected function setEntityTypeManager(EntityTypeManagerInterface $entityTypeManager) { $this->entityTypeManager = $entityTypeManager; - $this->entityRepository = $entityRepository; } /** - * {@inheritdoc} + * Set the entity repository. + * + * @param \Drupal\Core\Entity\EntityRepositoryInterface $entityRepository + * The entity repository. */ - public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) { - return new static( - $configuration, - $pluginId, - $pluginDefinition, - $container->get('entity_type.bundle.info'), - $container->get('language_manager'), - $container->get('entity_type.manager'), - $container->get('entity.repository') - ); + protected function setEntityRepository(EntityRepositoryInterface $entityRepository) { + $this->entityRepository = $entityRepository; } /** @@ -123,20 +117,6 @@ protected function getEntityTranslationRenderer() { return NULL; } - /** - * {@inheritdoc} - */ - public function getEntityTypeManager() { - return $this->entityTypeManager; - } - - /** - * {@inheritdoc} - */ - public function getEntityRepository() { - return $this->entityRepository; - } - /** * {@inheritdoc} */ @@ -151,15 +131,22 @@ public function getEntityTypeId() { /** * {@inheritdoc} */ - protected function getEntityTypeBundleInfo() { - return $this->entityTypeBundleInfo; + protected function getLanguageManager() { + return $this->languageManager; } /** * {@inheritdoc} */ - protected function getLanguageManager() { - return $this->languageManager; + protected function getEntityTypeManager() { + return $this->entityTypeManager; + } + + /** + * {@inheritdoc} + */ + protected function getEntityRepository() { + return $this->entityRepository; } /** diff --git a/src/Plugin/views/row/GraphQLFieldRow.php b/src/Plugin/views/row/GraphQLFieldRow.php deleted file mode 100644 index 863b552..0000000 --- a/src/Plugin/views/row/GraphQLFieldRow.php +++ /dev/null @@ -1,234 +0,0 @@ -<?php - -namespace Drupal\graphql_views\Plugin\views\row; - -use Drupal\Core\Form\FormStateInterface; -use Drupal\views\ViewExecutable; -use Drupal\views\Plugin\views\display\DisplayPluginBase; -use Drupal\views\Plugin\views\row\RowPluginBase; - -/** - * Plugin which displays fields as raw data. - * - * @ViewsRow( - * id = "graphql_field", - * title = @Translation("Fields"), - * help = @Translation("Use fields as row data."), - * display_types = {"graphql"} - * ) - */ -class GraphQLFieldRow extends RowPluginBase { - - /** - * {@inheritdoc} - */ - protected $usesFields = TRUE; - - /** - * Stores an array of prepared field aliases from options. - * - * @var array - */ - protected $replacementAliases = []; - - /** - * Stores an array of options to determine if the raw field output is used. - * - * @var array - */ - protected $rawOutputOptions = []; - - /** - * Stores an array of field GrpahQL type. - * - * @var array - */ - protected $typeOptions = []; - - /** - * {@inheritdoc} - */ - public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) { - parent::init($view, $display, $options); - - if (!empty($this->options['field_options'])) { - $options = (array) $this->options['field_options']; - // Prepare a trimmed version of replacement aliases. - $aliases = static::extractFromOptionsArray('alias', $options); - $this->replacementAliases = array_filter(array_map('trim', $aliases)); - // Prepare an array of raw output field options. - $this->rawOutputOptions = static::extractFromOptionsArray('raw_output', $options); - $this->typeOptions = static::extractFromOptionsArray('type', $options); - } - } - - /** - * {@inheritdoc} - */ - protected function defineOptions() { - $options = parent::defineOptions(); - $options['field_options'] = ['default' => []]; - - return $options; - } - - /** - * {@inheritdoc} - */ - public function buildOptionsForm(&$form, FormStateInterface $form_state) { - parent::buildOptionsForm($form, $form_state); - - $form['field_options'] = [ - '#type' => 'table', - '#header' => [ - $this->t('Field'), - $this->t('Alias'), - $this->t('Raw output'), - $this->t('Type'), - ], - '#empty' => $this->t('You have no fields. Add some to your view.'), - '#tree' => TRUE, - ]; - - $options = $this->options['field_options']; - - if ($fields = $this->view->display_handler->getOption('fields')) { - foreach ($fields as $id => $field) { - // Don't show the field if it has been excluded. - if (!empty($field['exclude'])) { - continue; - } - - $form['field_options'][$id]['field'] = [ - '#markup' => $id, - ]; - - $form['field_options'][$id]['alias'] = [ - '#title' => $this->t('Alias for @id', ['@id' => $id]), - '#title_display' => 'invisible', - '#type' => 'textfield', - '#default_value' => isset($options[$id]['alias']) ? $options[$id]['alias'] : '', - '#element_validate' => [[$this, 'validateAliasName']], - ]; - - $form['field_options'][$id]['raw_output'] = [ - '#title' => $this->t('Raw output for @id', ['@id' => $id]), - '#title_display' => 'invisible', - '#type' => 'checkbox', - '#default_value' => isset($options[$id]['raw_output']) ? $options[$id]['raw_output'] : '', - ]; - - $form['field_options'][$id]['type'] = [ - '#type' => 'select', - '#options' => [ - 'String' => $this->t('String'), - 'Int' => $this->t('Int'), - 'Float' => $this->t('Float'), - 'Boolean' => $this->t('Boolean'), - ], - '#default_value' => isset($options[$id]['type']) ? $options[$id]['type'] : 'String', - ]; - } - } - } - - /** - * Form element validation handler. - */ - public function validateAliasName($element, FormStateInterface $form_state) { - if (preg_match('@[^A-Za-z0-9_-]+@', $element['#value'])) { - $form_state->setError($element, $this->t('The machine-readable name must contain only letters, numbers, dashes and underscores.')); - } - } - - /** - * {@inheritdoc} - */ - public function validateOptionsForm(&$form, FormStateInterface $form_state) { - // Collect an array of aliases to validate. - $aliases = static::extractFromOptionsArray('alias', $form_state->getValue(['row_options', 'field_options'])); - - // If array filter returns empty, no values have been entered. Unique keys - // should only be validated if we have some. - if (($filtered = array_filter($aliases)) && (array_unique($filtered) !== $filtered)) { - $form_state->setErrorByName('aliases', $this->t('All field aliases must be unique')); - } - } - - /** - * {@inheritdoc} - */ - public function render($row) { - $output = []; - - foreach ($this->view->field as $id => $field) { - // If the raw output option has been set, just get the raw value. - if (!empty($this->rawOutputOptions[$id])) { - $value = $field->getValue($row); - } - // Otherwise, pass this through the field advancedRender() method. - else { - $value = $field->advancedRender($row); - } - - // Omit excluded fields from the rendered output. - if (empty($field->options['exclude'])) { - $output[$this->getFieldKeyAlias($id)] = $value; - } - } - - return $output; - } - - /** - * Return an alias for a field ID, as set in the options form. - * - * @param string $id - * The field id to lookup an alias for. - * - * @return string - * The matches user entered alias, or the original ID if nothing is found. - */ - public function getFieldKeyAlias($id) { - if (isset($this->replacementAliases[$id])) { - return $this->replacementAliases[$id]; - } - - return $id; - } - - /** - * Return a GraphQL field type, as set in the options form. - * - * @param string $id - * The field id to lookup a type for. - * - * @return string - * The matches user entered type, or String. - */ - public function getFieldType($id) { - if (isset($this->typeOptions[$id])) { - return $this->typeOptions[$id]; - } - - return 'String'; - } - - /** - * Extracts a set of option values from a nested options array. - * - * @param string $key - * The key to extract from each array item. - * @param array $options - * The options array to return values from. - * - * @return array - * A regular one dimensional array of values. - */ - protected static function extractFromOptionsArray($key, array $options) { - return array_map(function($item) use ($key) { - return isset($item[$key]) ? $item[$key] : NULL; - }, $options); - } - -} diff --git a/src/Plugin/views/style/GraphQL.php b/src/Plugin/views/style/GraphQL.php index 08b98f2..10d48df 100644 --- a/src/Plugin/views/style/GraphQL.php +++ b/src/Plugin/views/style/GraphQL.php @@ -49,4 +49,5 @@ public function render() { return $rows; } + } diff --git a/src/ViewDeriverHelperTrait.php b/src/ViewDeriverHelperTrait.php deleted file mode 100644 index a64325b..0000000 --- a/src/ViewDeriverHelperTrait.php +++ /dev/null @@ -1,385 +0,0 @@ -<?php - -namespace Drupal\graphql_views; - -use Drupal\Component\Plugin\PluginManagerInterface; -use Drupal\Component\Utility\NestedArray; -use Drupal\graphql\Utility\StringHelper; -use Drupal\graphql_views\Plugin\views\row\GraphQLEntityRow; -use Drupal\graphql_views\Plugin\views\row\GraphQLFieldRow; -use Drupal\views\Plugin\views\display\DisplayPluginInterface; -use Drupal\views\ViewEntityInterface; - -/** - * Helper functions fot view derivers. - */ -trait ViewDeriverHelperTrait { - - /** - * Helper function to return the contextual filter argument if any exist. - * - * @param array $arguments - * The array of available arguments. - * @param string $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 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; - } - -} diff --git a/tests/modules/graphql_views_test/config/install/views.view.graphql_bundle_test.yml b/tests/modules/graphql_views_test/config/install/views.view.graphql_bundle_test.yml deleted file mode 100644 index 5808ca7..0000000 --- a/tests/modules/graphql_views_test/config/install/views.view.graphql_bundle_test.yml +++ /dev/null @@ -1,203 +0,0 @@ -uuid: 8abe765b-8d05-4b97-8a39-0be2ac93f439 -langcode: en -status: true -dependencies: - config: - - node.type.test - module: - - graphql - - node - - user -id: graphql_bundle_test -label: 'GraphQL Bundle Test' -module: views -description: '' -tag: '' -base_table: node_field_data -base_field: nid -core: 8.x -core_version_requirement: ^8 || ^9 -display: - default: - display_plugin: default - id: default - display_title: Master - position: 0 - display_options: - access: - type: perm - options: - perm: 'access content' - cache: - type: tag - options: { } - query: - type: views_query - options: - disable_sql_rewrite: false - distinct: false - replica: false - query_comment: '' - query_tags: { } - exposed_form: - type: basic - options: - submit_button: Apply - reset_button: false - reset_button_label: Reset - exposed_sorts_label: 'Sort by' - expose_sort_order: true - sort_asc_label: Asc - sort_desc_label: Desc - pager: - type: mini - options: - items_per_page: 10 - offset: 0 - id: 0 - total_pages: null - expose: - items_per_page: false - items_per_page_label: 'Items per page' - items_per_page_options: '5, 10, 25, 50' - items_per_page_options_all: false - items_per_page_options_all_label: '- All -' - offset: false - offset_label: Offset - tags: - previous: ‹‹ - next: ›› - style: - type: default - options: - grouping: { } - row_class: '' - default_row_class: true - uses_fields: false - row: - type: fields - options: - inline: { } - separator: '' - hide_empty: false - default_field_elements: true - fields: - title: - id: title - table: node_field_data - field: title - entity_type: node - entity_field: title - label: '' - alter: - alter_text: false - make_link: false - absolute: false - trim: false - word_boundary: false - ellipsis: false - strip_tags: false - html: false - hide_empty: false - empty_zero: false - settings: - link_to_entity: true - plugin_id: field - relationship: none - group_type: group - admin_label: '' - exclude: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_alter_empty: true - click_sort_column: value - type: string - group_column: value - group_columns: { } - group_rows: true - delta_limit: 0 - delta_offset: 0 - delta_reversed: false - delta_first_last: false - multi_type: separator - separator: ', ' - field_api_classes: false - filters: - status: - value: '1' - table: node_field_data - field: status - plugin_id: boolean - entity_type: node - entity_field: status - id: status - expose: - operator: '' - group: 1 - type: - id: type - table: node_field_data - field: type - value: - test: test - entity_type: node - entity_field: type - plugin_id: bundle - sorts: - nid: - id: nid - table: node_field_data - field: nid - relationship: none - group_type: group - admin_label: '' - order: ASC - exposed: false - expose: - label: '' - entity_type: node - entity_field: nid - plugin_id: standard - - header: { } - footer: { } - empty: { } - relationships: { } - arguments: { } - display_extenders: { } - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url.query_args - - 'user.node_grants:view' - - user.permissions - tags: { } - graphql_1: - display_plugin: graphql - id: graphql_1 - display_title: GraphQL - position: 1 - display_options: - display_extenders: { } - pager: - type: some - options: - items_per_page: 1 - offset: 0 - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - 'user.node_grants:view' - - user.permissions - tags: { } diff --git a/tests/modules/graphql_views_test/config/install/views.view.graphql_test.yml b/tests/modules/graphql_views_test/config/install/views.view.graphql_test.yml deleted file mode 100644 index 3c35fe8..0000000 --- a/tests/modules/graphql_views_test/config/install/views.view.graphql_test.yml +++ /dev/null @@ -1,720 +0,0 @@ -uuid: 287a1f02-3fd1-4b5b-8c77-76855fa7f308 -langcode: en -status: true -dependencies: - config: - - taxonomy.vocabulary.tags - module: - - graphql - - node - - taxonomy - - user -id: graphql_test -label: 'GraphQL Test' -module: views -description: 'Views configurations for GraphQL integration testing.' -tag: '' -base_table: node_field_data -base_field: nid -core: 8.x -core_version_requirement: ^8 || ^9 -display: - default: - display_plugin: default - id: default - display_title: Master - position: 0 - display_options: - access: - type: perm - options: - perm: 'access content' - cache: - type: tag - options: { } - query: - type: views_query - options: - disable_sql_rewrite: false - distinct: false - replica: false - query_comment: '' - query_tags: { } - exposed_form: - type: basic - options: - submit_button: Apply - reset_button: false - reset_button_label: Reset - exposed_sorts_label: 'Sort by' - expose_sort_order: true - sort_asc_label: Asc - sort_desc_label: Desc - pager: - type: mini - options: - items_per_page: 10 - offset: 0 - id: 0 - total_pages: null - expose: - items_per_page: false - items_per_page_label: 'Items per page' - items_per_page_options: '5, 10, 25, 50' - items_per_page_options_all: false - items_per_page_options_all_label: '- All -' - offset: false - offset_label: Offset - tags: - previous: ‹‹ - next: ›› - style: - type: default - options: - grouping: { } - row_class: '' - default_row_class: true - uses_fields: false - row: - type: graphql_entity - options: { } - fields: - title: - id: title - table: node_field_data - field: title - entity_type: node - entity_field: title - label: '' - alter: - alter_text: false - make_link: false - absolute: false - trim: false - word_boundary: false - ellipsis: false - strip_tags: false - html: false - hide_empty: false - empty_zero: false - settings: - link_to_entity: true - plugin_id: field - relationship: none - group_type: group - admin_label: '' - exclude: false - element_type: '' - element_class: '' - element_label_type: '' - element_label_class: '' - element_label_colon: true - element_wrapper_type: '' - element_wrapper_class: '' - element_default_classes: true - empty: '' - hide_alter_empty: true - click_sort_column: value - type: string - group_column: value - group_columns: { } - group_rows: true - delta_limit: 0 - delta_offset: 0 - delta_reversed: false - delta_first_last: false - multi_type: separator - separator: ', ' - field_api_classes: false - filters: - title: - id: title - table: node_field_data - field: title - relationship: none - group_type: group - admin_label: '' - operator: contains - value: '' - group: 1 - exposed: true - expose: - operator_id: title_op - label: Title - description: '' - use_operator: false - operator: title_op - identifier: title - required: false - remember: false - multiple: false - remember_roles: - authenticated: authenticated - anonymous: '0' - administrator: '0' - is_grouped: false - group_info: - label: '' - description: '' - identifier: '' - optional: true - widget: select - multiple: false - remember: false - default_group: All - default_group_multiple: { } - group_items: { } - entity_type: node - entity_field: title - plugin_id: string - field_tags_target_id: - id: field_tags_target_id - table: node__field_tags - field: field_tags_target_id - relationship: none - group_type: group - admin_label: Tags - operator: or - value: { } - group: 1 - exposed: true - expose: - operator_id: field_tags_target_id_op - label: Tags - description: '' - use_operator: false - operator: field_tags_target_id_op - identifier: field_tags - required: false - remember: false - multiple: true - remember_roles: - authenticated: authenticated - anonymous: '0' - administrator: '0' - reduce: false - is_grouped: false - group_info: - label: '' - description: '' - identifier: '' - optional: true - widget: select - multiple: false - remember: false - default_group: All - default_group_multiple: { } - group_items: { } - reduce_duplicates: false - type: select - limit: true - vid: tags - hierarchy: false - error_message: true - plugin_id: taxonomy_index_tid - type: - id: type - table: node_field_data - field: type - relationship: none - group_type: group - admin_label: '' - operator: in - value: - test: test - group: 1 - exposed: true - expose: - operator_id: type_op - label: 'Content type' - description: '' - use_operator: false - operator: type_op - identifier: node_type - required: false - remember: false - multiple: false - remember_roles: - authenticated: authenticated - anonymous: '0' - administrator: '0' - reduce: true - is_grouped: false - group_info: - label: '' - description: '' - identifier: '' - optional: true - widget: select - multiple: false - remember: false - default_group: All - default_group_multiple: { } - group_items: { } - entity_type: node - entity_field: type - plugin_id: bundle - filter_groups: - operator: AND - groups: - 1: AND - sorts: - nid: - id: nid - table: node_field_data - field: nid - relationship: none - group_type: group - admin_label: '' - order: ASC - exposed: false - expose: - label: '' - entity_type: node - entity_field: nid - plugin_id: standard - header: { } - footer: { } - empty: { } - relationships: { } - arguments: { } - display_extenders: { } - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url - - url.query_args - - user - - 'user.node_grants:view' - - user.permissions - tags: { } - contextual_node: - display_plugin: graphql - id: contextual_node - display_title: 'Contextual: Node' - position: 6 - display_options: - display_extenders: { } - display_description: '' - arguments: - nid: - id: nid - table: node_field_data - field: nid - relationship: none - group_type: group - admin_label: '' - default_action: ignore - exception: - value: all - title_enable: false - title: All - title_enable: false - title: '' - default_argument_type: fixed - default_argument_options: - argument: '' - default_argument_skip_url: false - summary_options: - base_path: '' - count: true - items_per_page: 25 - override: false - summary: - sort_order: asc - number_of_records: 0 - format: default_summary - specify_validation: false - validate: - type: none - fail: 'not found' - validate_options: { } - break_phrase: false - not: false - entity_type: node - entity_field: nid - plugin_id: node_nid - defaults: - arguments: false - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url - - user - - 'user.node_grants:view' - - user.permissions - tags: { } - contextual_node_and_nodetest: - display_plugin: graphql - id: contextual_node_and_nodetest - display_title: 'Contextual: Node and NodeTest' - position: 8 - display_options: - display_extenders: { } - display_description: '' - arguments: - nid: - id: nid - table: node_field_data - field: nid - relationship: none - group_type: group - admin_label: '' - default_action: ignore - exception: - value: all - title_enable: false - title: All - title_enable: false - title: '' - default_argument_type: fixed - default_argument_options: - argument: '' - default_argument_skip_url: false - summary_options: - base_path: '' - count: true - items_per_page: 25 - override: false - summary: - sort_order: asc - number_of_records: 0 - format: default_summary - specify_validation: false - validate: - type: none - fail: 'not found' - validate_options: { } - break_phrase: false - not: false - entity_type: node - entity_field: nid - plugin_id: node_nid - nid_1: - id: nid_1 - table: node_field_data - field: nid - relationship: none - group_type: group - admin_label: '' - default_action: ignore - exception: - value: all - title_enable: false - title: All - title_enable: false - title: '' - default_argument_type: fixed - default_argument_options: - argument: '' - default_argument_skip_url: false - summary_options: - base_path: '' - count: true - items_per_page: 25 - override: false - summary: - sort_order: asc - number_of_records: 0 - format: default_summary - specify_validation: true - validate: - type: 'entity:node' - fail: 'not found' - validate_options: - bundles: - test: test - operation: view - multiple: 0 - access: false - break_phrase: false - not: false - entity_type: node - entity_field: nid - plugin_id: node_nid - defaults: - arguments: false - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url - - user - - 'user.node_grants:view' - - user.permissions - tags: { } - contextual_nodetest: - display_plugin: graphql - id: contextual_nodetest - display_title: 'Contextual: NodeTest' - position: 7 - display_options: - display_extenders: { } - display_description: '' - arguments: - nid: - id: nid - table: node_field_data - field: nid - relationship: none - group_type: group - admin_label: '' - default_action: ignore - exception: - value: all - title_enable: false - title: All - title_enable: false - title: '' - default_argument_type: fixed - default_argument_options: - argument: '' - default_argument_skip_url: false - summary_options: - base_path: '' - count: true - items_per_page: 25 - override: false - summary: - sort_order: asc - number_of_records: 0 - format: default_summary - specify_validation: true - validate: - type: 'entity:node' - fail: 'not found' - validate_options: - bundles: - test: test - operation: view - multiple: 0 - access: false - break_phrase: false - not: false - entity_type: node - entity_field: nid - plugin_id: node_nid - defaults: - arguments: false - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url - - user - - 'user.node_grants:view' - - user.permissions - tags: { } - contextual_title_arg: - display_plugin: graphql - id: contextual_title_arg - display_title: 'Contextual: title arg' - position: 5 - display_options: - display_extenders: { } - arguments: - title: - id: title - table: node_field_data - field: title - relationship: none - group_type: group - admin_label: '' - default_action: ignore - exception: - value: all - title_enable: false - title: All - title_enable: false - title: '' - default_argument_type: fixed - default_argument_options: - argument: '' - default_argument_skip_url: false - summary_options: - base_path: '' - count: true - items_per_page: 25 - override: false - summary: - sort_order: asc - number_of_records: 0 - format: default_summary - specify_validation: false - validate: - type: none - fail: 'not found' - validate_options: { } - glossary: false - limit: 0 - case: none - path_case: none - transform_dash: false - break_phrase: false - entity_type: node - entity_field: title - plugin_id: string - defaults: - arguments: false - display_description: '' - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url - - user - - 'user.node_grants:view' - - user.permissions - tags: { } - filtered: - display_plugin: graphql - id: filtered - display_title: Filtered - position: 4 - display_options: - display_extenders: { } - display_description: '' - filters: { } - defaults: - filters: true - filter_groups: true - pager: - type: none - options: - offset: 0 - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url - - user - - 'user.node_grants:view' - - user.permissions - tags: { } - paged: - display_plugin: graphql - id: paged - display_title: Paged - position: 2 - display_options: - display_extenders: { } - display_description: '' - pager: - type: full - options: - items_per_page: 2 - offset: 0 - id: 0 - total_pages: null - tags: - previous: '‹ Previous' - next: 'Next ›' - first: '« First' - last: 'Last »' - expose: - items_per_page: false - items_per_page_label: 'Items per page' - items_per_page_options: '5, 10, 25, 50' - items_per_page_options_all: false - items_per_page_options_all_label: '- All -' - offset: false - offset_label: Offset - quantity: 9 - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url - - url.query_args - - user - - 'user.node_grants:view' - - user.permissions - tags: { } - simple: - display_plugin: graphql - id: simple - display_title: Simple - position: 1 - display_options: - display_extenders: { } - display_description: '' - pager: - type: some - options: - items_per_page: 3 - offset: 0 - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url - - user - - 'user.node_grants:view' - - user.permissions - tags: { } - sorted: - display_plugin: graphql - id: sorted - display_title: Sorted - position: 3 - display_options: - display_extenders: { } - display_description: '' - sorts: - nid: - id: nid - table: node_field_data - field: nid - relationship: none - group_type: group - admin_label: '' - order: ASC - exposed: true - expose: - label: ID - entity_type: node - entity_field: nid - plugin_id: standard - title: - id: title - table: node_field_data - field: title - relationship: none - group_type: group - admin_label: '' - order: ASC - exposed: true - expose: - label: Title - entity_type: node - entity_field: title - plugin_id: standard - defaults: - sorts: false - pager: - type: some - options: - items_per_page: 3 - offset: 0 - cache_metadata: - max-age: -1 - contexts: - - 'languages:language_content' - - 'languages:language_interface' - - url - - 'url.query_args:sort_by' - - 'url.query_args:sort_order' - - user - - 'user.node_grants:view' - - user.permissions - tags: { } diff --git a/tests/modules/graphql_views_test/graphql_views_test.features.yml b/tests/modules/graphql_views_test/graphql_views_test.features.yml deleted file mode 100644 index 060a98e..0000000 --- a/tests/modules/graphql_views_test/graphql_views_test.features.yml +++ /dev/null @@ -1 +0,0 @@ -required: true diff --git a/tests/modules/graphql_views_test/graphql_views_test.info.yml b/tests/modules/graphql_views_test/graphql_views_test.info.yml deleted file mode 100644 index 552dca4..0000000 --- a/tests/modules/graphql_views_test/graphql_views_test.info.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: 'GraphQL Views Test' -description: 'Test configurations for GraphQL views integration.' -type: module -core: 8.x -core_version_requirement: ^8 || ^9 -dependencies: - - graphql_views - - graphql_content_test - - node - - taxonomy - - user - - views diff --git a/tests/modules/graphql_views_test/graphql_views_test.module b/tests/modules/graphql_views_test/graphql_views_test.module deleted file mode 100644 index 513c30b..0000000 --- a/tests/modules/graphql_views_test/graphql_views_test.module +++ /dev/null @@ -1,12 +0,0 @@ -<?php - -use Drupal\views\ViewExecutable; - -function graphql_views_test_views_pre_build(ViewExecutable $view) { - $args =& drupal_static('graphql_views_test:view:args', []); - $id = $view->storage->id() . ':' . $view->current_display; - if (!isset($args[$id])) { - $args[$id] = []; - } - $args[$id][] = $view->args; -} diff --git a/tests/queries/contextual.gql b/tests/queries/contextual.gql deleted file mode 100644 index c4b1e69..0000000 --- a/tests/queries/contextual.gql +++ /dev/null @@ -1,151 +0,0 @@ -query ($test2NodeId: String!) { - - # graphql_test:contextual_title_arg - title_arg0:graphqlTestContextualTitleArgView { - results { - entityId - } - } - - title_arg1:graphqlTestContextualTitleArgView (contextualFilter: {title: "X"}) { - results { - entityId - } - } - - # graphql_test:contextual_node - node0:graphqlTestContextualNodeView { - results { - entityId - } - } - - node1:graphqlTestContextualNodeView (contextualFilter: {nid: "X"}) { - results { - entityId - } - } - - node2:nodeById (id: "1", language: EN) { - ... on Node { - graphqlTestContextualNodeView { - results { - entityId - } - } - } - } - node3:nodeById (id: "1", language: EN) { - ... on Node { - graphqlTestContextualNodeView (contextualFilter: {nid: "X"}) { - results { - entityId - } - } - } - } - node4:nodeById (id: "1", language: EN) { - ... on NodeTest { - graphqlTestContextualNodeView { - results { - entityId - } - } - } - } - node5:nodeById (id: "1", language: EN) { - ... on NodeTest { - graphqlTestContextualNodeView (contextualFilter: {nid: "X"}) { - results { - entityId - } - } - } - } - - # graphql_test:contextual_nodetest - nodetest0:graphqlTestContextualNodetestView { - results { - entityId - } - } - - nodetest1:graphqlTestContextualNodetestView (contextualFilter: {nid: "X"}) { - results { - entityId - } - } - - nodetest2:nodeById (id: "1", language: EN) { - ... on NodeTest { - graphqlTestContextualNodetestView { - results { - entityId - } - } - } - } - - nodetest3:nodeById (id: "1", language: EN) { - ... on NodeTest { - graphqlTestContextualNodetestView (contextualFilter: {nid: "X"}) { - results { - entityId - } - } - } - } - - # graphql_test:contextual_node_and_nodetest - node_and_nodetest0:graphqlTestContextualNodeAndNodetestView { - results { - entityId - } - } - - node_and_nodetest1:graphqlTestContextualNodeAndNodetestView (contextualFilter: {nid: "X", nid_1: "X"}) { - results { - entityId - } - } - - node_and_nodetest2:nodeById (id: $test2NodeId, language: EN) { - ... on Node { - graphqlTestContextualNodeAndNodetestView { - results { - entityId - } - } - } - } - node_and_nodetest3:nodeById (id: $test2NodeId, language: EN) { - ... on Node { - graphqlTestContextualNodeAndNodetestView (contextualFilter: {nid: "X", nid_1: "X"}) { - results { - entityId - } - } - } - } - - node_and_nodetest4:nodeById (id: "1", language: EN) { - ... on NodeTest { - graphqlTestContextualNodeAndNodetestView { - results { - entityId - } - } - } - } - - node_and_nodetest5:nodeById (id: "1", language: EN) { - ... on NodeTest { - graphqlTestContextualNodeAndNodetestView (contextualFilter: {nid: "X", nid_1: "X"}) { - results { - entityId - } - } - } - } - -} diff --git a/tests/queries/paged.gql b/tests/queries/paged.gql deleted file mode 100644 index ce4a2ea..0000000 --- a/tests/queries/paged.gql +++ /dev/null @@ -1,29 +0,0 @@ -{ - page_one:graphqlTestPagedView { - count - results { - entityLabel - } - } - - page_two:graphqlTestPagedView(page: 1) { - count - results { - entityLabel - } - } - - page_three:graphqlTestPagedView(page: 2, pageSize: 3) { - count - results { - entityLabel - } - } - - page_four:graphqlTestPagedView(page: 2 pageSize: 4) { - count - results { - entityLabel - } - } -} \ No newline at end of file diff --git a/tests/queries/simple.gql b/tests/queries/simple.gql deleted file mode 100644 index 8c928df..0000000 --- a/tests/queries/simple.gql +++ /dev/null @@ -1,7 +0,0 @@ -{ - graphqlTestSimpleView { - results { - entityLabel - } - } -} \ No newline at end of file diff --git a/tests/queries/single_bundle_filter.gql b/tests/queries/single_bundle_filter.gql deleted file mode 100644 index e1ad866..0000000 --- a/tests/queries/single_bundle_filter.gql +++ /dev/null @@ -1,7 +0,0 @@ -{ - withSingleBundleFilter: graphqlBundleTestGraphql1View { - results { - __typename - } - } -} diff --git a/tests/queries/sorted.gql b/tests/queries/sorted.gql deleted file mode 100644 index 68e5d8f..0000000 --- a/tests/queries/sorted.gql +++ /dev/null @@ -1,31 +0,0 @@ -{ - default:graphqlTestSortedView { - results { - entityLabel - } - } - - asc:graphqlTestSortedView(sortBy: TITLE) { - results { - entityLabel - } - } - - desc:graphqlTestSortedView(sortBy: TITLE, sortDirection: DESC) { - results { - entityLabel - } - } - - asc_nid:graphqlTestSortedView(sortBy: NID, sortDirection: ASC) { - results { - entityLabel - } - } - - desc_nid:graphqlTestSortedView(sortBy: NID, sortDirection: DESC) { - results { - entityLabel - } - } -} diff --git a/tests/src/Kernel/ContextualViewsTest.php b/tests/src/Kernel/ContextualViewsTest.php deleted file mode 100644 index 87fdf5e..0000000 --- a/tests/src/Kernel/ContextualViewsTest.php +++ /dev/null @@ -1,114 +0,0 @@ -<?php - -namespace Drupal\Tests\graphql_views\Kernel; - -use GraphQL\Server\OperationParams; - -/** - * Test contextual views support in GraphQL. - * - * @group graphql_views - */ -class ContextualViewsTest extends ViewsTestBase { - - /** - * {@inheritdoc} - */ - protected function setUp() { - parent::setUp(); - $this->createContentType(['type' => 'test2']); - } - - /** - * {@inheritdoc} - */ - protected function defaultCacheContexts() { - return array_merge([ - 'languages:language_content', - 'languages:language_interface', - 'user.permissions', - 'user.node_grants:view', - ], parent::defaultCacheContexts()); - } - - /** - * {@inheritdoc} - */ - protected function defaultCacheTags() { - return array_merge([ - 'config:field.storage.node.field_tags', - ], parent::defaultCacheTags()); - } - - /** - * Test if view contextual filters are set properly. - */ - public function testContextualViewArgs() { - $test2Node = $this->createNode(['type' => 'test2']); - - $this->graphQlProcessor()->processQuery( - $this->getDefaultSchema(), - OperationParams::create([ - 'query' => $this->getQueryFromFile('contextual.gql'), - 'variables' => ['test2NodeId' => $test2Node->id()], - ]) - ); - - $this->assertEquals(drupal_static('graphql_views_test:view:args'), [ - 'graphql_test:contextual_title_arg' => [ - 0 => [NULL], - 1 => ['X'], - ], - 'graphql_test:contextual_node' => [ - 0 => [NULL], - 1 => ['X'], - 2 => ['1'], - 3 => ['X'], - 4 => ['1'], - 5 => ['X'], - ], - 'graphql_test:contextual_nodetest' => [ - 0 => [NULL], - 1 => ['X'], - 2 => ['1'], - 3 => ['X'], - ], - 'graphql_test:contextual_node_and_nodetest' => [ - 0 => [NULL, NULL], - 1 => ['X', 'X'], - 2 => [$test2Node->id(), NULL], - 3 => ['X', 'X'], - 4 => ['1', '1'], - 5 => ['X', 'X'], - ], - ]); - } - - /** - * Test if view fields are attached to correct types. - */ - public function testContextualViewFields() { - $schema = $this->introspect(); - - $field = 'graphqlTestContextualTitleArgView'; - $this->assertArrayHasKey($field, $schema['types']['Query']['fields']); - $this->assertArrayNotHasKey($field, $schema['types']['Node']['fields']); - $this->assertArrayNotHasKey($field, $schema['types']['NodeTest']['fields']); - - $field = 'graphqlTestContextualNodeView'; - $this->assertArrayHasKey($field, $schema['types']['Query']['fields']); - $this->assertArrayHasKey($field, $schema['types']['Node']['fields']); - $this->assertArrayHasKey($field, $schema['types']['NodeTest']['fields']); - - $field = 'graphqlTestContextualNodetestView'; - $this->assertArrayHasKey($field, $schema['types']['Query']['fields']); - $this->assertArrayNotHasKey($field, $schema['types']['Node']['fields']); - $this->assertArrayHasKey($field, $schema['types']['NodeTest']['fields']); - - $field = 'graphqlTestContextualNodeAndNodetestView'; - $this->assertArrayHasKey($field, $schema['types']['Query']['fields']); - $this->assertArrayHasKey($field, $schema['types']['Node']['fields']); - $this->assertArrayHasKey($field, $schema['types']['NodeTest']['fields']); - } - -} diff --git a/tests/src/Kernel/ViewsTest.php b/tests/src/Kernel/ViewsTest.php deleted file mode 100644 index add0dd1..0000000 --- a/tests/src/Kernel/ViewsTest.php +++ /dev/null @@ -1,282 +0,0 @@ -<?php - -namespace Drupal\Tests\graphql_views\Kernel; - - -/** - * Test views support in GraphQL. - * - * @group graphql_views - */ -class ViewsTest extends ViewsTestBase { - - /** - * {@inheritdoc} - */ - protected function defaultCacheContexts() { - return array_merge([ - 'user.permissions', - 'user.node_grants:view', - ], parent::defaultCacheContexts()); - } - - /** - * Test that the view returns both nodes. - */ - public function testSimpleView() { - $query = $this->getQueryFromFile('simple.gql'); - $this->assertResults($query, [], [ - 'graphqlTestSimpleView' => [ - 'results' => [ - [ - 'entityLabel' => 'Node A', - ], [ - 'entityLabel' => 'Node B', - ], [ - 'entityLabel' => 'Node C', - ], - ], - ], - ], $this->defaultCacheMetaData()->addCacheTags([ - 'config:views.view.graphql_test', - 'node:1', - 'node:2', - 'node:3', - 'node_list', - ])->addCacheContexts(['user'])); - } - - /** - * Test paging support. - */ - public function testPagedView() { - $query = $this->getQueryFromFile('paged.gql'); - $this->assertResults($query, [], [ - 'page_one' => [ - 'count' => count($this->letters), - 'results' => [ - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node B'], - ], - ], - 'page_two' => [ - 'count' => count($this->letters), - 'results' => [ - ['entityLabel' => 'Node C'], - ['entityLabel' => 'Node A'], - ], - ], - 'page_three' => [ - 'count' => count($this->letters), - 'results' => [ - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node B'], - ['entityLabel' => 'Node C'], - ], - ], - 'page_four' => [ - 'count' => count($this->letters), - 'results' => [ - ['entityLabel' => 'Node C'], - ], - ], - ], $this->defaultCacheMetaData()->addCacheTags([ - 'config:views.view.graphql_test', - 'node:1', - 'node:2', - 'node:3', - 'node:4', - 'node:7', - 'node:8', - 'node:9', - 'node_list', - ])->addCacheContexts(['user'])); - } - - /** - * Test sorting behavior. - */ - public function testSortedView() { - $query = $this->getQueryFromFile('sorted.gql'); - $this->assertResults($query, [], [ - 'default' => [ - 'results' => [ - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node B'], - ['entityLabel' => 'Node C'], - ], - ], - 'asc' => [ - 'results' => [ - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node A'], - ], - ], - 'desc' => [ - 'results' => [ - ['entityLabel' => 'Node C'], - ['entityLabel' => 'Node C'], - ['entityLabel' => 'Node C'], - ], - ], - 'asc_nid' => [ - 'results' => [ - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node B'], - ['entityLabel' => 'Node C'], - ], - ], - 'desc_nid' => [ - 'results' => [ - ['entityLabel' => 'Node C'], - ['entityLabel' => 'Node B'], - ['entityLabel' => 'Node A'], - ], - ], - ], $this->defaultCacheMetaData()->addCacheTags([ - 'config:views.view.graphql_test', - 'node:1', - 'node:2', - 'node:3', - 'node:4', - 'node:6', - 'node:7', - 'node:8', - 'node:9', - 'node_list', - ])->addCacheContexts(['user'])); - } - - /** - * Test filter behavior. - */ - public function testFilteredView() { - $query = <<<GQL -query { - default:graphqlTestFilteredView (filter: {title: "A"}) { - results { - entityLabel - } - } -} - -GQL; - - $this->assertResults($query, [], [ - 'default' => [ - 'results' => [ - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node A'], - ], - ], - ], $this->defaultCacheMetaData()->addCacheTags([ - 'config:views.view.graphql_test', - 'node:1', - 'node:4', - 'node:7', - 'node_list', - ])->addCacheContexts(['user'])); - } - - /** - * Test filter behavior. - */ - public function testMultiValueFilteredView() { - $query = <<<GQL -query { - multi:graphqlTestFilteredView (filter: {field_tags: ["1", "2"]}) { - results { - entityLabel - } - } -} -GQL; - $this->assertResults($query, [], [ - 'multi' => [ - 'results' => [ - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node B'], - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node B'], - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node B'], - ], - ], - ], $this->defaultCacheMetaData()->addCacheTags([ - 'config:views.view.graphql_test', - 'node:1', - 'node:2', - 'node:4', - 'node:5', - 'node:7', - 'node:8', - 'node_list', - - ])->addCacheContexts(['user'])); - } - - /** - * Test complex filters. - */ - public function testComplexFilteredView() { - $query = <<<GQL -query { - complex:graphqlTestFilteredView(filter: {node_type:{test:"test"}}) { - results { - entityLabel - } - } -} -GQL; - $this->assertResults($query, [], [ - 'complex' => [ - 'results' => [ - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node B'], - ['entityLabel' => 'Node C'], - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node B'], - ['entityLabel' => 'Node C'], - ['entityLabel' => 'Node A'], - ['entityLabel' => 'Node B'], - ['entityLabel' => 'Node C'], - ], - ], - ], $this->defaultCacheMetaData()->addCacheTags([ - 'config:views.view.graphql_test', - 'node:1', - 'node:2', - 'node:3', - 'node:4', - 'node:5', - 'node:6', - 'node:7', - 'node:8', - 'node:9', - 'node_list', - ])->addCacheContexts(['user'])); - } - - /** - * Test the result type for views with a single-value bundle filter. - */ - public function testSingleValueBundleFilterView() { - $query = $this->getQueryFromFile('single_bundle_filter.gql'); - $this->assertResults($query, [], [ - 'withSingleBundleFilter' => [ - 'results' => [ - 0 => [ - '__typename' => 'NodeTest', - ], - ], - ], - ], $this->defaultCacheMetaData()->addCacheTags([ - 'config:views.view.graphql_bundle_test', - 'node:1', - 'node_list', - ])); - } - -} diff --git a/tests/src/Kernel/ViewsTestBase.php b/tests/src/Kernel/ViewsTestBase.php deleted file mode 100644 index ca35782..0000000 --- a/tests/src/Kernel/ViewsTestBase.php +++ /dev/null @@ -1,88 +0,0 @@ -<?php - -namespace Drupal\Tests\graphql_views\Kernel; - -use Drupal\taxonomy\Entity\Term; -use Drupal\taxonomy\Entity\Vocabulary; -use Drupal\Tests\field\Traits\EntityReferenceTestTrait; -use Drupal\Tests\graphql_core\Kernel\GraphQLContentTestBase; -use Drupal\Tests\node\Traits\ContentTypeCreationTrait; -use Drupal\Tests\node\Traits\NodeCreationTrait; - -/** - * Base class for test views support in GraphQL. - * - * @group graphql_views - */ -abstract class ViewsTestBase extends GraphQLContentTestBase { - use NodeCreationTrait; - use ContentTypeCreationTrait; - use EntityReferenceTestTrait; - - /** - * {@inheritdoc} - */ - public static $modules = [ - 'node', - 'field', - 'filter', - 'text', - 'views', - 'taxonomy', - 'graphql_core', - 'graphql_views', - 'graphql_views_test', - ]; - - /** - * A List of letters. - * - * @var string[] - */ - protected $letters = ['A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'C']; - - /** - * {@inheritdoc} - */ - protected function setUp() { - parent::setUp(); - $this->installEntitySchema('view'); - $this->installEntitySchema('taxonomy_term'); - $this->installConfig(['node', 'filter', 'views', 'graphql_views_test']); - $this->createEntityReferenceField('node', 'test', 'field_tags', 'Tags', 'taxonomy_term'); - - Vocabulary::create([ - 'name' => 'Tags', - 'vid' => 'tags', - ])->save(); - - $terms = []; - - $terms['A'] = Term::create([ - 'name' => 'Term A', - 'vid' => 'tags', - ]); - $terms['A']->save(); - - $terms['B'] = Term::create([ - 'name' => 'Term B', - 'vid' => 'tags', - ]); - $terms['B']->save(); - - $terms['C'] = Term::create([ - 'name' => 'Term C', - 'vid' => 'tags', - ]); - $terms['C']->save(); - - foreach ($this->letters as $index => $letter) { - $this->createNode([ - 'title' => 'Node ' . $letter, - 'type' => 'test', - 'field_tags' => $terms[$letter], - ])->save(); - } - } - -} From cfc03d216120cdd547bf54a15099f841c4b962a7 Mon Sep 17 00:00:00 2001 From: Christian Fritsch <chr.fritsch@gmx.net> Date: Wed, 9 Jun 2021 12:14:07 +0200 Subject: [PATCH 02/10] fix: add tests --- .travis.yml | 104 +++ config/schema/graphql_views.views.schema.yml | 7 + src/Plugin/GraphQL/DataProducer/Views.php | 127 ++- src/Plugin/views/display/GraphQL.php | 7 +- .../views.view.graphql_bundle_test.yml | 203 +++++ .../install/views.view.graphql_test.yml | 720 ++++++++++++++++++ .../graphql_views_test.features.yml | 1 + .../graphql_views_test.info.yml | 12 + .../graphql_views_test.module | 12 + tests/queries/contextual.gql | 151 ++++ tests/queries/paged.gql | 29 + tests/queries/simple.gql | 7 + tests/queries/single_bundle_filter.gql | 7 + tests/queries/sorted.gql | 31 + tests/src/Kernel/ContextualViewsTest.php | 114 +++ tests/src/Kernel/ViewsTest.php | 548 +++++++++++++ tests/src/Kernel/ViewsTestBase.php | 87 +++ 17 files changed, 2163 insertions(+), 4 deletions(-) create mode 100644 .travis.yml create mode 100644 config/schema/graphql_views.views.schema.yml create mode 100644 tests/modules/graphql_views_test/config/install/views.view.graphql_bundle_test.yml create mode 100644 tests/modules/graphql_views_test/config/install/views.view.graphql_test.yml create mode 100644 tests/modules/graphql_views_test/graphql_views_test.features.yml create mode 100644 tests/modules/graphql_views_test/graphql_views_test.info.yml create mode 100644 tests/modules/graphql_views_test/graphql_views_test.module create mode 100644 tests/queries/contextual.gql create mode 100644 tests/queries/paged.gql create mode 100644 tests/queries/simple.gql create mode 100644 tests/queries/single_bundle_filter.gql create mode 100644 tests/queries/sorted.gql create mode 100644 tests/src/Kernel/ContextualViewsTest.php create mode 100644 tests/src/Kernel/ViewsTest.php create mode 100644 tests/src/Kernel/ViewsTestBase.php diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8069089 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,104 @@ +language: php +sudo: false + +php: + - 7.4 + - 7.3 + +services: + - mysql + +env: + global: + - DRUPAL_GRAPHQL=8.x-4.x + - DRUPAL_BUILD_DIR=$TRAVIS_BUILD_DIR/../drupal + - SIMPLETEST_DB=mysql://root:@127.0.0.1/graphql + - TRAVIS=true + matrix: + - DRUPAL_CORE=9.1.x + - DRUPAL_CORE=8.9.x + +matrix: + # Don't wait for the allowed failures to build. + fast_finish: true + include: + - php: 7.3 + env: + - DRUPAL_CORE=9.1.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.3 + env: + - DRUPAL_CORE=9.1.x + # Only run code coverage on the latest php and drupal versions. + - WITH_PHPDBG_COVERAGE=true + +mysql: + database: graphql + username: root + encoding: utf8 + +# Cache composer downloads. +cache: + directories: + - $HOME/.composer + +before_install: + # Disable xdebug. + - phpenv config-rm xdebug.ini + + # Determine the php settings file location. + - if [[ $TRAVIS_PHP_VERSION = hhvm* ]]; + then export PHPINI=/etc/hhvm/php.ini; + else export PHPINI=$HOME/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; + fi + + # Disable the default memory limit. + - echo memory_limit = -1 >> $PHPINI + + # Update composer. + - composer self-update + +install: + # Create the database. + - mysql -e 'create database graphql' + + # Download Drupal 8 core from the Github mirror because it is faster. + - git clone --branch $DRUPAL_CORE --depth 1 https://github.com/drupal/drupal.git $DRUPAL_BUILD_DIR + - git clone --branch $DRUPAL_GRAPHQL --depth 1 https://github.com/drupal-graphql/graphql.git $DRUPAL_BUILD_DIR/modules/graphql + + # Reference the module in the build site. + - ln -s $TRAVIS_BUILD_DIR $DRUPAL_BUILD_DIR/modules/graphql_views + + # Copy the customized phpunit configuration file to the core directory so + # the relative paths are correct. + - cp $DRUPAL_BUILD_DIR/modules/graphql/phpunit.xml.dist $DRUPAL_BUILD_DIR/core/phpunit.xml + + # When running with phpdbg we need to replace all code occurrences that check + # for 'cli' with 'phpdbg'. Some files might be write protected, hence the + # fallback. + - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]]; + then grep -rl 'cli' $DRUPAL_BUILD_DIR/core $DRUPAL_BUILD_DIR/modules | xargs sed -i "s/'cli'/'phpdbg'/g" || true; + fi + + # 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.5 + +script: + # Run the unit tests using phpdbg if the environment variable is 'true'. + - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]]; + then phpdbg -qrr $DRUPAL_BUILD_DIR/vendor/bin/phpunit --configuration $DRUPAL_BUILD_DIR/core/phpunit.xml --coverage-clover $TRAVIS_BUILD_DIR/coverage.xml $TRAVIS_BUILD_DIR; + fi + + # Run the unit tests with standard php otherwise. + - if [[ "$WITH_PHPDBG_COVERAGE" != "true" ]]; + then $DRUPAL_BUILD_DIR/vendor/bin/phpunit --configuration $DRUPAL_BUILD_DIR/core/phpunit.xml $TRAVIS_BUILD_DIR; + fi + +after_success: + - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]]; + then bash <(curl -s https://codecov.io/bash); + fi diff --git a/config/schema/graphql_views.views.schema.yml b/config/schema/graphql_views.views.schema.yml new file mode 100644 index 0000000..3c55ce4 --- /dev/null +++ b/config/schema/graphql_views.views.schema.yml @@ -0,0 +1,7 @@ +views.display.graphql: + type: views_display + label: 'GraphQL display options' + mapping: + graphql_query_name: + type: string + label: 'GraphQL query name' diff --git a/src/Plugin/GraphQL/DataProducer/Views.php b/src/Plugin/GraphQL/DataProducer/Views.php index 100fd9b..f445a81 100644 --- a/src/Plugin/GraphQL/DataProducer/Views.php +++ b/src/Plugin/GraphQL/DataProducer/Views.php @@ -2,8 +2,10 @@ namespace Drupal\graphql_views\Plugin\GraphQL\DataProducer; +use Drupal\Component\Utility\NestedArray; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\graphql\GraphQL\Execution\FieldContext; use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase; use Drupal\views\Plugin\views\display\DisplayPluginInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -36,6 +38,19 @@ * "page" = @ContextDefinition("integer", * label = @Translation("Current page"), * required = FALSE + * ), + * "sort_by" = @ContextDefinition("string", + * label = @Translation("Sort by"), + * required = FALSE + * ), + * "sort_direction" = @ContextDefinition("string", + * label = @Translation("Sort direction"), + * required = FALSE + * ), + * "filter" = @ContextDefinition("any", + * label = @Translation("Sort direction"), + * required = FALSE, + * default_value = {} * ) * } * ) @@ -84,7 +99,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ - public function resolve(string $view_id, string $display_id, int $offset = NULL, int $page_size = NULL, int $page = NULL) { + public function resolve(string $view_id, string $display_id, int $offset = NULL, int $page_size = NULL, int $page = NULL, string $sort_by = NULL, string $sort_direction = NULL, array $filter = [], FieldContext $fieldContext) { /** @var \Drupal\views\Entity\View $view */ $view = \Drupal::entityTypeManager()->getStorage('view')->load($view_id); @@ -93,7 +108,13 @@ public function resolve(string $view_id, string $display_id, int $offset = NULL, $executable->setDisplay($display_id); // Set paging parameters. - if ($this->isPaged($executable->getDisplay()) && $page_size && $page) { + if (empty($page_size)) { + $page_size = $this->getPagerLimit($executable->getDisplay()); + } + if (empty($page)) { + $page = $this->getPagerOffset($executable->getDisplay()); + } + if ($this->isPaged($executable->getDisplay())) { $executable->setItemsPerPage($page_size); $executable->setCurrentPage($page); } @@ -102,9 +123,37 @@ public function resolve(string $view_id, string $display_id, int $offset = NULL, $executable->setOffset($offset); } + $available_filters = $executable->getDisplay()->getOption('filters'); + $input = $this->extractExposedInput($sort_by, $sort_direction, $filter, $available_filters); + $executable->setExposedInput($input); + + // This is a workaround for the Taxonomy Term filter which requires a full + // exposed form to be sent OR the display being an attachment to just + // accept input values. + $executable->is_attachment = TRUE; + $executable->exposed_raw_input = $input; + $executable->preExecute(); $executable->execute(); - return $executable->render($display_id); + + $result = $executable->render($display_id); + + + /** @var \Drupal\Core\Cache\CacheableMetadata $cache */ + if ($cache = $result['cache']) { + $cache->setCacheContexts( + array_filter($cache->getCacheContexts(), function ($context) { + // Don't emit the url cache contexts. + return $context !== 'url' && strpos($context, 'url.') !== 0; + }) + ); + $fieldContext->addCacheableDependency($cache); + } + + return [ + 'results' => $result['rows'], + 'count' => $result['view']->total_rows, + ]; } /** @@ -124,4 +173,76 @@ protected function isPaged(DisplayPluginInterface $display) { ]); } + /** + * 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 sort and filter arguments from the provided field args. + * + * @param $value + * The resolved parent value. + * @param $args + * The array of arguments provided to the field. + * @param $available_filters + * The available filters for the configured view. + * + * @return array + * The array of sort and filter arguments to execute the view with. + */ + protected function extractExposedInput($sort_by, $sort_direction, $filter, $available_filters) { + // Prepare arguments for use as exposed form input. + $input = array_filter([ + // Sorting arguments. + 'sort_by' => $sort_by, + 'sort_order' => $sort_direction, + ]); + + // If some filters are missing from the input, set them to an empty string + // explicitly. Otherwise views module generates "Undefined index" notice. + foreach ($available_filters as $filterRow) { + if (!isset($filterRow['expose']['identifier'])) { + continue; + } + + $inputKey = $filterRow['expose']['identifier']; + if (!isset($filter[$inputKey])) { + $input[$inputKey] = $filterRow['value']; + } else { + $input[$inputKey] = $filter[$inputKey]; + } + } + return $input; + } + } diff --git a/src/Plugin/views/display/GraphQL.php b/src/Plugin/views/display/GraphQL.php index 664c05e..4c3da1d 100644 --- a/src/Plugin/views/display/GraphQL.php +++ b/src/Plugin/views/display/GraphQL.php @@ -2,6 +2,7 @@ namespace Drupal\graphql_views\Plugin\views\display; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\views\Plugin\views\display\DisplayPluginBase; /** @@ -137,7 +138,11 @@ public function render() { // cache render array so we have to transform it back afterwards. $this->applyDisplayCacheabilityMetadata($this->view->element); - return $rows; + return [ + 'view' => $this->view, + 'rows' => $rows, + 'cache' => CacheableMetadata::createFromRenderArray($this->view->element), + ]; } } diff --git a/tests/modules/graphql_views_test/config/install/views.view.graphql_bundle_test.yml b/tests/modules/graphql_views_test/config/install/views.view.graphql_bundle_test.yml new file mode 100644 index 0000000..5808ca7 --- /dev/null +++ b/tests/modules/graphql_views_test/config/install/views.view.graphql_bundle_test.yml @@ -0,0 +1,203 @@ +uuid: 8abe765b-8d05-4b97-8a39-0be2ac93f439 +langcode: en +status: true +dependencies: + config: + - node.type.test + module: + - graphql + - node + - user +id: graphql_bundle_test +label: 'GraphQL Bundle Test' +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +core: 8.x +core_version_requirement: ^8 || ^9 +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + title: + id: title + table: node_field_data + field: title + entity_type: node + entity_field: title + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + settings: + link_to_entity: true + plugin_id: field + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + type: string + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + filters: + status: + value: '1' + table: node_field_data + field: status + plugin_id: boolean + entity_type: node + entity_field: status + id: status + expose: + operator: '' + group: 1 + type: + id: type + table: node_field_data + field: type + value: + test: test + entity_type: node + entity_field: type + plugin_id: bundle + sorts: + nid: + id: nid + table: node_field_data + field: nid + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: false + expose: + label: '' + entity_type: node + entity_field: nid + plugin_id: standard + + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } + graphql_1: + display_plugin: graphql + id: graphql_1 + display_title: GraphQL + position: 1 + display_options: + display_extenders: { } + pager: + type: some + options: + items_per_page: 1 + offset: 0 + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/tests/modules/graphql_views_test/config/install/views.view.graphql_test.yml b/tests/modules/graphql_views_test/config/install/views.view.graphql_test.yml new file mode 100644 index 0000000..3c35fe8 --- /dev/null +++ b/tests/modules/graphql_views_test/config/install/views.view.graphql_test.yml @@ -0,0 +1,720 @@ +uuid: 287a1f02-3fd1-4b5b-8c77-76855fa7f308 +langcode: en +status: true +dependencies: + config: + - taxonomy.vocabulary.tags + module: + - graphql + - node + - taxonomy + - user +id: graphql_test +label: 'GraphQL Test' +module: views +description: 'Views configurations for GraphQL integration testing.' +tag: '' +base_table: node_field_data +base_field: nid +core: 8.x +core_version_requirement: ^8 || ^9 +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: graphql_entity + options: { } + fields: + title: + id: title + table: node_field_data + field: title + entity_type: node + entity_field: title + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + settings: + link_to_entity: true + plugin_id: field + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + type: string + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + filters: + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: title_op + label: Title + description: '' + use_operator: false + operator: title_op + identifier: title + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: node + entity_field: title + plugin_id: string + field_tags_target_id: + id: field_tags_target_id + table: node__field_tags + field: field_tags_target_id + relationship: none + group_type: group + admin_label: Tags + operator: or + value: { } + group: 1 + exposed: true + expose: + operator_id: field_tags_target_id_op + label: Tags + description: '' + use_operator: false + operator: field_tags_target_id_op + identifier: field_tags + required: false + remember: false + multiple: true + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + reduce_duplicates: false + type: select + limit: true + vid: tags + hierarchy: false + error_message: true + plugin_id: taxonomy_index_tid + type: + id: type + table: node_field_data + field: type + relationship: none + group_type: group + admin_label: '' + operator: in + value: + test: test + group: 1 + exposed: true + expose: + operator_id: type_op + label: 'Content type' + description: '' + use_operator: false + operator: type_op + identifier: node_type + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: true + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: node + entity_field: type + plugin_id: bundle + filter_groups: + operator: AND + groups: + 1: AND + sorts: + nid: + id: nid + table: node_field_data + field: nid + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: false + expose: + label: '' + entity_type: node + entity_field: nid + plugin_id: standard + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + - user.permissions + tags: { } + contextual_node: + display_plugin: graphql + id: contextual_node + display_title: 'Contextual: Node' + position: 6 + display_options: + display_extenders: { } + display_description: '' + arguments: + nid: + id: nid + table: node_field_data + field: nid + relationship: none + group_type: group + admin_label: '' + default_action: ignore + exception: + value: all + title_enable: false + title: All + title_enable: false + title: '' + default_argument_type: fixed + default_argument_options: + argument: '' + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: false + validate: + type: none + fail: 'not found' + validate_options: { } + break_phrase: false + not: false + entity_type: node + entity_field: nid + plugin_id: node_nid + defaults: + arguments: false + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - user + - 'user.node_grants:view' + - user.permissions + tags: { } + contextual_node_and_nodetest: + display_plugin: graphql + id: contextual_node_and_nodetest + display_title: 'Contextual: Node and NodeTest' + position: 8 + display_options: + display_extenders: { } + display_description: '' + arguments: + nid: + id: nid + table: node_field_data + field: nid + relationship: none + group_type: group + admin_label: '' + default_action: ignore + exception: + value: all + title_enable: false + title: All + title_enable: false + title: '' + default_argument_type: fixed + default_argument_options: + argument: '' + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: false + validate: + type: none + fail: 'not found' + validate_options: { } + break_phrase: false + not: false + entity_type: node + entity_field: nid + plugin_id: node_nid + nid_1: + id: nid_1 + table: node_field_data + field: nid + relationship: none + group_type: group + admin_label: '' + default_action: ignore + exception: + value: all + title_enable: false + title: All + title_enable: false + title: '' + default_argument_type: fixed + default_argument_options: + argument: '' + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: true + validate: + type: 'entity:node' + fail: 'not found' + validate_options: + bundles: + test: test + operation: view + multiple: 0 + access: false + break_phrase: false + not: false + entity_type: node + entity_field: nid + plugin_id: node_nid + defaults: + arguments: false + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - user + - 'user.node_grants:view' + - user.permissions + tags: { } + contextual_nodetest: + display_plugin: graphql + id: contextual_nodetest + display_title: 'Contextual: NodeTest' + position: 7 + display_options: + display_extenders: { } + display_description: '' + arguments: + nid: + id: nid + table: node_field_data + field: nid + relationship: none + group_type: group + admin_label: '' + default_action: ignore + exception: + value: all + title_enable: false + title: All + title_enable: false + title: '' + default_argument_type: fixed + default_argument_options: + argument: '' + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: true + validate: + type: 'entity:node' + fail: 'not found' + validate_options: + bundles: + test: test + operation: view + multiple: 0 + access: false + break_phrase: false + not: false + entity_type: node + entity_field: nid + plugin_id: node_nid + defaults: + arguments: false + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - user + - 'user.node_grants:view' + - user.permissions + tags: { } + contextual_title_arg: + display_plugin: graphql + id: contextual_title_arg + display_title: 'Contextual: title arg' + position: 5 + display_options: + display_extenders: { } + arguments: + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + default_action: ignore + exception: + value: all + title_enable: false + title: All + title_enable: false + title: '' + default_argument_type: fixed + default_argument_options: + argument: '' + default_argument_skip_url: false + summary_options: + base_path: '' + count: true + items_per_page: 25 + override: false + summary: + sort_order: asc + number_of_records: 0 + format: default_summary + specify_validation: false + validate: + type: none + fail: 'not found' + validate_options: { } + glossary: false + limit: 0 + case: none + path_case: none + transform_dash: false + break_phrase: false + entity_type: node + entity_field: title + plugin_id: string + defaults: + arguments: false + display_description: '' + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - user + - 'user.node_grants:view' + - user.permissions + tags: { } + filtered: + display_plugin: graphql + id: filtered + display_title: Filtered + position: 4 + display_options: + display_extenders: { } + display_description: '' + filters: { } + defaults: + filters: true + filter_groups: true + pager: + type: none + options: + offset: 0 + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - user + - 'user.node_grants:view' + - user.permissions + tags: { } + paged: + display_plugin: graphql + id: paged + display_title: Paged + position: 2 + display_options: + display_extenders: { } + display_description: '' + pager: + type: full + options: + items_per_page: 2 + offset: 0 + id: 0 + total_pages: null + tags: + previous: '‹ Previous' + next: 'Next ›' + first: '« First' + last: 'Last »' + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + quantity: 9 + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user + - 'user.node_grants:view' + - user.permissions + tags: { } + simple: + display_plugin: graphql + id: simple + display_title: Simple + position: 1 + display_options: + display_extenders: { } + display_description: '' + pager: + type: some + options: + items_per_page: 3 + offset: 0 + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - user + - 'user.node_grants:view' + - user.permissions + tags: { } + sorted: + display_plugin: graphql + id: sorted + display_title: Sorted + position: 3 + display_options: + display_extenders: { } + display_description: '' + sorts: + nid: + id: nid + table: node_field_data + field: nid + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: true + expose: + label: ID + entity_type: node + entity_field: nid + plugin_id: standard + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: true + expose: + label: Title + entity_type: node + entity_field: title + plugin_id: standard + defaults: + sorts: false + pager: + type: some + options: + items_per_page: 3 + offset: 0 + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - 'url.query_args:sort_by' + - 'url.query_args:sort_order' + - user + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/tests/modules/graphql_views_test/graphql_views_test.features.yml b/tests/modules/graphql_views_test/graphql_views_test.features.yml new file mode 100644 index 0000000..060a98e --- /dev/null +++ b/tests/modules/graphql_views_test/graphql_views_test.features.yml @@ -0,0 +1 @@ +required: true diff --git a/tests/modules/graphql_views_test/graphql_views_test.info.yml b/tests/modules/graphql_views_test/graphql_views_test.info.yml new file mode 100644 index 0000000..552dca4 --- /dev/null +++ b/tests/modules/graphql_views_test/graphql_views_test.info.yml @@ -0,0 +1,12 @@ +name: 'GraphQL Views Test' +description: 'Test configurations for GraphQL views integration.' +type: module +core: 8.x +core_version_requirement: ^8 || ^9 +dependencies: + - graphql_views + - graphql_content_test + - node + - taxonomy + - user + - views diff --git a/tests/modules/graphql_views_test/graphql_views_test.module b/tests/modules/graphql_views_test/graphql_views_test.module new file mode 100644 index 0000000..513c30b --- /dev/null +++ b/tests/modules/graphql_views_test/graphql_views_test.module @@ -0,0 +1,12 @@ +<?php + +use Drupal\views\ViewExecutable; + +function graphql_views_test_views_pre_build(ViewExecutable $view) { + $args =& drupal_static('graphql_views_test:view:args', []); + $id = $view->storage->id() . ':' . $view->current_display; + if (!isset($args[$id])) { + $args[$id] = []; + } + $args[$id][] = $view->args; +} diff --git a/tests/queries/contextual.gql b/tests/queries/contextual.gql new file mode 100644 index 0000000..c4b1e69 --- /dev/null +++ b/tests/queries/contextual.gql @@ -0,0 +1,151 @@ +query ($test2NodeId: String!) { + + # graphql_test:contextual_title_arg + title_arg0:graphqlTestContextualTitleArgView { + results { + entityId + } + } + + title_arg1:graphqlTestContextualTitleArgView (contextualFilter: {title: "X"}) { + results { + entityId + } + } + + # graphql_test:contextual_node + node0:graphqlTestContextualNodeView { + results { + entityId + } + } + + node1:graphqlTestContextualNodeView (contextualFilter: {nid: "X"}) { + results { + entityId + } + } + + node2:nodeById (id: "1", language: EN) { + ... on Node { + graphqlTestContextualNodeView { + results { + entityId + } + } + } + } + node3:nodeById (id: "1", language: EN) { + ... on Node { + graphqlTestContextualNodeView (contextualFilter: {nid: "X"}) { + results { + entityId + } + } + } + } + node4:nodeById (id: "1", language: EN) { + ... on NodeTest { + graphqlTestContextualNodeView { + results { + entityId + } + } + } + } + node5:nodeById (id: "1", language: EN) { + ... on NodeTest { + graphqlTestContextualNodeView (contextualFilter: {nid: "X"}) { + results { + entityId + } + } + } + } + + # graphql_test:contextual_nodetest + nodetest0:graphqlTestContextualNodetestView { + results { + entityId + } + } + + nodetest1:graphqlTestContextualNodetestView (contextualFilter: {nid: "X"}) { + results { + entityId + } + } + + nodetest2:nodeById (id: "1", language: EN) { + ... on NodeTest { + graphqlTestContextualNodetestView { + results { + entityId + } + } + } + } + + nodetest3:nodeById (id: "1", language: EN) { + ... on NodeTest { + graphqlTestContextualNodetestView (contextualFilter: {nid: "X"}) { + results { + entityId + } + } + } + } + + # graphql_test:contextual_node_and_nodetest + node_and_nodetest0:graphqlTestContextualNodeAndNodetestView { + results { + entityId + } + } + + node_and_nodetest1:graphqlTestContextualNodeAndNodetestView (contextualFilter: {nid: "X", nid_1: "X"}) { + results { + entityId + } + } + + node_and_nodetest2:nodeById (id: $test2NodeId, language: EN) { + ... on Node { + graphqlTestContextualNodeAndNodetestView { + results { + entityId + } + } + } + } + node_and_nodetest3:nodeById (id: $test2NodeId, language: EN) { + ... on Node { + graphqlTestContextualNodeAndNodetestView (contextualFilter: {nid: "X", nid_1: "X"}) { + results { + entityId + } + } + } + } + + node_and_nodetest4:nodeById (id: "1", language: EN) { + ... on NodeTest { + graphqlTestContextualNodeAndNodetestView { + results { + entityId + } + } + } + } + + node_and_nodetest5:nodeById (id: "1", language: EN) { + ... on NodeTest { + graphqlTestContextualNodeAndNodetestView (contextualFilter: {nid: "X", nid_1: "X"}) { + results { + entityId + } + } + } + } + +} diff --git a/tests/queries/paged.gql b/tests/queries/paged.gql new file mode 100644 index 0000000..8f4bfbe --- /dev/null +++ b/tests/queries/paged.gql @@ -0,0 +1,29 @@ +{ + page_one:graphqlTestPagedView { + count + results { + entityLabel + } + } + + page_two:graphqlTestPagedView(page: 1) { + count + results { + entityLabel + } + } + + page_three:graphqlTestPagedView(page: 2, pageSize: 3) { + count + results { + entityLabel + } + } + + page_four:graphqlTestPagedView(page: 2 pageSize: 4) { + count + results { + entityLabel + } + } +} diff --git a/tests/queries/simple.gql b/tests/queries/simple.gql new file mode 100644 index 0000000..e3f8474 --- /dev/null +++ b/tests/queries/simple.gql @@ -0,0 +1,7 @@ +{ + graphqlTestSimpleView { + results { + entityLabel + } + } +} diff --git a/tests/queries/single_bundle_filter.gql b/tests/queries/single_bundle_filter.gql new file mode 100644 index 0000000..e1ad866 --- /dev/null +++ b/tests/queries/single_bundle_filter.gql @@ -0,0 +1,7 @@ +{ + withSingleBundleFilter: graphqlBundleTestGraphql1View { + results { + __typename + } + } +} diff --git a/tests/queries/sorted.gql b/tests/queries/sorted.gql new file mode 100644 index 0000000..68e5d8f --- /dev/null +++ b/tests/queries/sorted.gql @@ -0,0 +1,31 @@ +{ + default:graphqlTestSortedView { + results { + entityLabel + } + } + + asc:graphqlTestSortedView(sortBy: TITLE) { + results { + entityLabel + } + } + + desc:graphqlTestSortedView(sortBy: TITLE, sortDirection: DESC) { + results { + entityLabel + } + } + + asc_nid:graphqlTestSortedView(sortBy: NID, sortDirection: ASC) { + results { + entityLabel + } + } + + desc_nid:graphqlTestSortedView(sortBy: NID, sortDirection: DESC) { + results { + entityLabel + } + } +} diff --git a/tests/src/Kernel/ContextualViewsTest.php b/tests/src/Kernel/ContextualViewsTest.php new file mode 100644 index 0000000..34fa2f7 --- /dev/null +++ b/tests/src/Kernel/ContextualViewsTest.php @@ -0,0 +1,114 @@ +<?php + +namespace Drupal\Tests\graphql_views\Kernel; + +use GraphQL\Server\OperationParams; + +/** + * Test contextual views support in GraphQL. + * + * @group graphql_views + */ +class ContextualViewsTest extends ViewsTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->createContentType(['type' => 'test2']); + } + + /** + * {@inheritdoc} + */ + protected function defaultCacheContexts(): array { + return array_merge([ + 'languages:language_content', + 'languages:language_interface', + 'user.permissions', + 'user.node_grants:view', + ], parent::defaultCacheContexts()); + } + + /** + * {@inheritdoc} + */ + protected function defaultCacheTags(): array { + return array_merge([ + 'config:field.storage.node.field_tags', + ], parent::defaultCacheTags()); + } + + /** + * Test if view contextual filters are set properly. + */ + public function testContextualViewArgs() { + $test2Node = $this->createNode(['type' => 'test2']); + + $this->graphQlProcessor()->processQuery( + $this->getDefaultSchema(), + OperationParams::create([ + 'query' => $this->getQueryFromFile('contextual.gql'), + 'variables' => ['test2NodeId' => $test2Node->id()], + ]) + ); + + $this->assertEquals(drupal_static('graphql_views_test:view:args'), [ + 'graphql_test:contextual_title_arg' => [ + 0 => [NULL], + 1 => ['X'], + ], + 'graphql_test:contextual_node' => [ + 0 => [NULL], + 1 => ['X'], + 2 => ['1'], + 3 => ['X'], + 4 => ['1'], + 5 => ['X'], + ], + 'graphql_test:contextual_nodetest' => [ + 0 => [NULL], + 1 => ['X'], + 2 => ['1'], + 3 => ['X'], + ], + 'graphql_test:contextual_node_and_nodetest' => [ + 0 => [NULL, NULL], + 1 => ['X', 'X'], + 2 => [$test2Node->id(), NULL], + 3 => ['X', 'X'], + 4 => ['1', '1'], + 5 => ['X', 'X'], + ], + ]); + } + + /** + * Test if view fields are attached to correct types. + */ + public function testContextualViewFields() { + $schema = $this->introspect(); + + $field = 'graphqlTestContextualTitleArgView'; + $this->assertArrayHasKey($field, $schema['types']['Query']['fields']); + $this->assertArrayNotHasKey($field, $schema['types']['Node']['fields']); + $this->assertArrayNotHasKey($field, $schema['types']['NodeTest']['fields']); + + $field = 'graphqlTestContextualNodeView'; + $this->assertArrayHasKey($field, $schema['types']['Query']['fields']); + $this->assertArrayHasKey($field, $schema['types']['Node']['fields']); + $this->assertArrayHasKey($field, $schema['types']['NodeTest']['fields']); + + $field = 'graphqlTestContextualNodetestView'; + $this->assertArrayHasKey($field, $schema['types']['Query']['fields']); + $this->assertArrayNotHasKey($field, $schema['types']['Node']['fields']); + $this->assertArrayHasKey($field, $schema['types']['NodeTest']['fields']); + + $field = 'graphqlTestContextualNodeAndNodetestView'; + $this->assertArrayHasKey($field, $schema['types']['Query']['fields']); + $this->assertArrayHasKey($field, $schema['types']['Node']['fields']); + $this->assertArrayHasKey($field, $schema['types']['NodeTest']['fields']); + } + +} diff --git a/tests/src/Kernel/ViewsTest.php b/tests/src/Kernel/ViewsTest.php new file mode 100644 index 0000000..e01c015 --- /dev/null +++ b/tests/src/Kernel/ViewsTest.php @@ -0,0 +1,548 @@ +<?php + +namespace Drupal\Tests\graphql_views\Kernel; + +/** + * Test views support in GraphQL. + * + * @group graphql_views + */ +class ViewsTest extends ViewsTestBase { + + /** + * {@inheritdoc} + */ + protected function defaultCacheContexts(): array { + return array_merge([ + 'languages:language_content', + 'languages:language_interface', + 'user.permissions', + 'user.node_grants:view', + ], parent::defaultCacheContexts()); + } + + /** + * Test that the view returns both nodes. + */ + public function testSimpleView() { + $schema = <<<GQL + type Query { + graphqlTestSimpleView(page: Int, pageSize: Int): ViewResult + } + + type ViewResult { + results: [Node], + count: Int + } + + type Node { + entityLabel: String! + } +GQL; + + $this->setUpSchema($schema); + + $this->mockResolver('Query', 'graphqlTestSimpleView', + $this->builder->produce('views') + ->map('view_id', $this->builder->fromValue('graphql_test')) + ->map('display_id', $this->builder->fromValue('simple')) + ->map('page', $this->builder->fromArgument('page')) + ->map('page_size', $this->builder->fromArgument('pageSize')) + ); + + $this->mockResolver('Node', 'entityLabel', + $this->builder->produce('entity_label') + ->map('entity', $this->builder->fromParent()) + ); + + $query = $this->getQueryFromFile('simple.gql'); + $this->assertResults($query, [], [ + 'graphqlTestSimpleView' => [ + 'results' => [ + [ + 'entityLabel' => 'Node A', + ], [ + 'entityLabel' => 'Node B', + ], [ + 'entityLabel' => 'Node C', + ], + ], + ], + ], $this->defaultCacheMetaData()->addCacheTags([ + 'config:views.view.graphql_test', + 'node:1', + 'node:2', + 'node:3', + 'node_list', + ])->addCacheContexts(['user'])); + } + + /** + * Test paging support. + */ + public function testPagedView() { + $schema = <<<GQL + type Query { + graphqlTestPagedView(page: Int, pageSize: Int): ViewResult + } + + type ViewResult { + results: [Node], + count: Int + } + + type Node { + entityLabel: String! + } +GQL; + + $this->setUpSchema($schema); + + $this->mockResolver('Query', 'graphqlTestPagedView', + $this->builder->produce('views') + ->map('view_id', $this->builder->fromValue('graphql_test')) + ->map('display_id', $this->builder->fromValue('paged')) + ->map('page', $this->builder->fromArgument('page')) + ->map('page_size', $this->builder->fromArgument('pageSize')) + ); + + $this->mockResolver('Node', 'entityLabel', + $this->builder->produce('entity_label') + ->map('entity', $this->builder->fromParent()) + ); + + $query = $this->getQueryFromFile('paged.gql'); + $this->assertResults($query, [], [ + 'page_one' => [ + 'count' => count($this->letters), + 'results' => [ + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node B'], + ], + ], + 'page_two' => [ + 'count' => count($this->letters), + 'results' => [ + ['entityLabel' => 'Node C'], + ['entityLabel' => 'Node A'], + ], + ], + 'page_three' => [ + 'count' => count($this->letters), + 'results' => [ + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node B'], + ['entityLabel' => 'Node C'], + ], + ], + 'page_four' => [ + 'count' => count($this->letters), + 'results' => [ + ['entityLabel' => 'Node C'], + ], + ], + ], $this->defaultCacheMetaData()->addCacheTags([ + 'config:views.view.graphql_test', + 'node:1', + 'node:2', + 'node:3', + 'node:4', + 'node:7', + 'node:8', + 'node:9', + 'node_list', + ])->addCacheContexts(['user'])); + } + + /** + * Test sorting behavior. + */ + public function testSortedView() { + $schema = <<<GQL + type Query { + graphqlTestSortedView(page: Int, pageSize: Int, sortBy: SortBy, sortDirection: SortDirection): ViewResult + } + + enum SortDirection { + ASC + DESC + } + + enum SortBy { + TITLE + NID + } + + type ViewResult { + results: [Node], + count: Int + } + + type Node { + entityLabel: String! + } +GQL; + + $this->setUpSchema($schema); + + $this->mockResolver('Query', 'graphqlTestSortedView', + $this->builder->produce('views') + ->map('view_id', $this->builder->fromValue('graphql_test')) + ->map('display_id', $this->builder->fromValue('sorted')) + ->map('sort_direction', $this->builder->fromArgument('sortDirection')) + ->map('sort_by', $this->builder->compose( + $this->builder->fromArgument('sortBy'), + $this->builder->callback(function ($direction) { + $map = ['TITLE' => 'title', 'NID' => 'nid']; + return $map[$direction] ?? NULL; + }) + )) + ); + + $this->mockResolver('Node', 'entityLabel', + $this->builder->produce('entity_label') + ->map('entity', $this->builder->fromParent()) + ); + + $query = $this->getQueryFromFile('sorted.gql'); + $this->assertResults($query, [], [ + 'default' => [ + 'results' => [ + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node B'], + ['entityLabel' => 'Node C'], + ], + ], + 'asc' => [ + 'results' => [ + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node A'], + ], + ], + 'desc' => [ + 'results' => [ + ['entityLabel' => 'Node C'], + ['entityLabel' => 'Node C'], + ['entityLabel' => 'Node C'], + ], + ], + 'asc_nid' => [ + 'results' => [ + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node B'], + ['entityLabel' => 'Node C'], + ], + ], + 'desc_nid' => [ + 'results' => [ + ['entityLabel' => 'Node C'], + ['entityLabel' => 'Node B'], + ['entityLabel' => 'Node A'], + ], + ], + ], $this->defaultCacheMetaData()->addCacheTags([ + 'config:views.view.graphql_test', + 'node:1', + 'node:2', + 'node:3', + 'node:4', + 'node:6', + 'node:7', + 'node:8', + 'node:9', + 'node_list', + ])->addCacheContexts(['user'])); + } + + /** + * Test filter behavior. + */ + public function testFilteredView() { + $schema = <<<GQL + type Query { + graphqlTestFilteredView(filter: Filter): ViewResult + } + + input Filter { + TITLE: String + NID: Int + } + + type ViewResult { + results: [Node], + count: Int + } + + type Node { + entityLabel: String! + } +GQL; + $this->setUpSchema($schema); + + $this->mockResolver('Query', 'graphqlTestFilteredView', + $this->builder->produce('views') + ->map('view_id', $this->builder->fromValue('graphql_test')) + ->map('display_id', $this->builder->fromValue('filtered')) + ->map('filter', $this->builder->compose( + $this->builder->fromArgument('filter'), + $this->builder->callback(function ($filter) { + $mapped = []; + $map = ['TITLE' => 'title', 'NID' => 'nid']; + foreach ($filter as $key => $value) { + if (isset($map[$key])) { + $mapped = [$map[$key] => $value]; + } + } + return $mapped; + }) + )) + ); + + $this->mockResolver('Node', 'entityLabel', + $this->builder->produce('entity_label') + ->map('entity', $this->builder->fromParent()) + ); + + $query = <<<GQL +query { + default:graphqlTestFilteredView (filter: {TITLE: "A"}) { + results { + entityLabel + } + } +} +GQL; + + $this->assertResults($query, [], [ + 'default' => [ + 'results' => [ + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node A'], + ], + ], + ], $this->defaultCacheMetaData()->addCacheTags([ + 'config:views.view.graphql_test', + 'node:1', + 'node:4', + 'node:7', + 'node_list', + ])->addCacheContexts(['user'])); + } + + /** + * Test filter behavior. + */ + public function testMultiValueFilteredView() { + $schema = <<<GQL + type Query { + graphqlTestFilteredView(filter: Filter): ViewResult + } + + input Filter { + field_tags: [Int] + } + + type ViewResult { + results: [Node], + count: Int + } + + type Node { + entityLabel: String! + } +GQL; + $this->setUpSchema($schema); + + $this->mockResolver('Query', 'graphqlTestFilteredView', + $this->builder->produce('views') + ->map('view_id', $this->builder->fromValue('graphql_test')) + ->map('display_id', $this->builder->fromValue('filtered')) + ->map('filter', $this->builder->compose( + $this->builder->fromArgument('filter'), + $this->builder->callback(function ($filter) { + $mapped = []; + $map = ['field_tags' => 'field_tags']; + foreach ($filter as $key => $value) { + if (isset($map[$key])) { + $mapped = [$map[$key] => $value]; + } + } + return $mapped; + }) + )) + ); + + $this->mockResolver('Node', 'entityLabel', + $this->builder->produce('entity_label') + ->map('entity', $this->builder->fromParent()) + ); + + $query = <<<GQL +query { + multi:graphqlTestFilteredView (filter: {field_tags: [1, 2]}) { + results { + entityLabel + } + } +} +GQL; + $this->assertResults($query, [], [ + 'multi' => [ + 'results' => [ + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node B'], + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node B'], + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node B'], + ], + ], + ], $this->defaultCacheMetaData()->addCacheTags([ + 'config:views.view.graphql_test', + 'node:1', + 'node:2', + 'node:4', + 'node:5', + 'node:7', + 'node:8', + 'node_list', + + ])->addCacheContexts(['user'])); + } + + /** + * Test complex filters. + */ + public function testComplexFilteredView() { + $schema = <<<GQL + type Query { + graphqlTestFilteredView(filter: Filter): ViewResult + } + + input Filter { + node_type: [String] + } + + type ViewResult { + results: [Node], + count: Int + } + + type Node { + entityLabel: String! + } +GQL; + $this->setUpSchema($schema); + + $this->mockResolver('Query', 'graphqlTestFilteredView', + $this->builder->produce('views') + ->map('view_id', $this->builder->fromValue('graphql_test')) + ->map('display_id', $this->builder->fromValue('filtered')) + ->map('filter', $this->builder->compose( + $this->builder->fromArgument('filter'), + $this->builder->callback(function ($filter) { + $mapped = []; + $map = ['node_type' => 'node_type']; + foreach ($filter as $key => $value) { + if (isset($map[$key])) { + $mapped = [$map[$key] => $value]; + } + } + return $mapped; + }) + )) + ); + + $this->mockResolver('Node', 'entityLabel', + $this->builder->produce('entity_label') + ->map('entity', $this->builder->fromParent()) + ); + + $query = <<<GQL +query { + complex:graphqlTestFilteredView(filter: {node_type:["test"]}) { + results { + entityLabel + } + } +} +GQL; + $this->assertResults($query, [], [ + 'complex' => [ + 'results' => [ + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node B'], + ['entityLabel' => 'Node C'], + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node B'], + ['entityLabel' => 'Node C'], + ['entityLabel' => 'Node A'], + ['entityLabel' => 'Node B'], + ['entityLabel' => 'Node C'], + ], + ], + ], $this->defaultCacheMetaData()->addCacheTags([ + 'config:views.view.graphql_test', + 'node:1', + 'node:2', + 'node:3', + 'node:4', + 'node:5', + 'node:6', + 'node:7', + 'node:8', + 'node:9', + 'node_list', + ])->addCacheContexts(['user'])); + } + + /** + * Test the result type for views with a single-value bundle filter. + */ + public function testSingleValueBundleFilterView() { + $schema = <<<GQL + type Query { + graphqlBundleTestGraphql1View: ViewResult + } + + type ViewResult { + results: [NodeTest], + count: Int + } + + type NodeTest { + entityLabel: String! + } +GQL; + $this->setUpSchema($schema); + + $this->mockResolver('Query', 'graphqlBundleTestGraphql1View', + $this->builder->produce('views') + ->map('view_id', $this->builder->fromValue('graphql_bundle_test')) + ->map('display_id', $this->builder->fromValue('graphql_1')) + ); + + $this->mockResolver('Node', 'entityLabel', + $this->builder->produce('entity_label') + ->map('entity', $this->builder->fromParent()) + ); + + $query = $this->getQueryFromFile('single_bundle_filter.gql'); + $this->assertResults($query, [], [ + 'withSingleBundleFilter' => [ + 'results' => [ + 0 => [ + '__typename' => 'NodeTest', + ], + ], + ], + ], $this->defaultCacheMetaData()->addCacheTags([ + 'config:views.view.graphql_bundle_test', + 'node:1', + 'node_list', + ])); + } + +} diff --git a/tests/src/Kernel/ViewsTestBase.php b/tests/src/Kernel/ViewsTestBase.php new file mode 100644 index 0000000..be1c244 --- /dev/null +++ b/tests/src/Kernel/ViewsTestBase.php @@ -0,0 +1,87 @@ +<?php + +namespace Drupal\Tests\graphql_views\Kernel; + +use Drupal\taxonomy\Entity\Term; +use Drupal\taxonomy\Entity\Vocabulary; +use Drupal\Tests\field\Traits\EntityReferenceTestTrait; +use Drupal\Tests\graphql\Kernel\GraphQLTestBase; +use Drupal\Tests\node\Traits\ContentTypeCreationTrait; +use Drupal\Tests\node\Traits\NodeCreationTrait; + +/** + * Base class for test views support in GraphQL. + * + * @group graphql_views + */ +abstract class ViewsTestBase extends GraphQLTestBase { + use NodeCreationTrait; + use ContentTypeCreationTrait; + use EntityReferenceTestTrait; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'filter', + 'text', + 'views', + 'taxonomy', + 'graphql_views', + 'graphql_views_test', + ]; + + /** + * A List of letters. + * + * @var string[] + */ + protected $letters = ['A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'C']; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->installEntitySchema('view'); + $this->installEntitySchema('taxonomy_term'); + $this->installConfig(['node', 'filter', 'views', 'graphql_views_test']); + $this->createContentType(['type' => 'test']); + $this->createEntityReferenceField('node', 'test', 'field_tags', 'Tags', 'taxonomy_term'); + + Vocabulary::create([ + 'name' => 'Tags', + 'vid' => 'tags', + ])->save(); + + $terms = []; + + $terms['A'] = Term::create([ + 'name' => 'Term A', + 'vid' => 'tags', + ]); + $terms['A']->save(); + + $terms['B'] = Term::create([ + 'name' => 'Term B', + 'vid' => 'tags', + ]); + $terms['B']->save(); + + $terms['C'] = Term::create([ + 'name' => 'Term C', + 'vid' => 'tags', + ]); + $terms['C']->save(); + + foreach ($this->letters as $letter) { + $this->createNode([ + 'title' => 'Node ' . $letter, + 'type' => 'test', + 'field_tags' => $terms[$letter], + ])->save(); + } + + } + +} From 1301bd16c9aeb1c9feaa7ec45b3cbcdeb9a8b53d Mon Sep 17 00:00:00 2001 From: Christian Fritsch <chr.fritsch@gmx.net> Date: Wed, 9 Jun 2021 12:18:14 +0200 Subject: [PATCH 03/10] fix: use github actions --- .github/workflows/reviewdog.yml | 29 +++++++++ .github/workflows/test.yml | 83 +++++++++++++++++++++++++ .travis.yml | 104 -------------------------------- 3 files changed, 112 insertions(+), 104 deletions(-) create mode 100644 .github/workflows/reviewdog.yml create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml new file mode 100644 index 0000000..c027d01 --- /dev/null +++ b/.github/workflows/reviewdog.yml @@ -0,0 +1,29 @@ +name: Check coding styles + +on: [pull_request] + +jobs: + phpcs: + name: runner / phpcs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + + - name: phpcs + uses: chrfritsch/action-drupal-coder@v1 + with: + github_token: ${{ secrets.github_token }} + level: error + filter_mode: nofilter + + misspell: + name: runner / misspell + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: reviewdog/action-misspell@v1 + with: + github_token: ${{ secrets.github_token }} + reporter: github-check + level: warning + locale: "US" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..1b137df --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,83 @@ +name: Run module tests + +on: + pull_request: + branches: + - 8.x-[1-9]+.x + paths-ignore: + - '**.md' + + schedule: + - cron: '0 6 * * *' + +env: + DRUPAL_TESTING_TEST_CODING_STYLES: false + DRUPAL_TESTING_DATABASE_USER: root + DRUPAL_TESTING_DATABASE_PASSWORD: root + DRUPAL_TESTING_DATABASE_ENGINE: mysql + DRUPAL_TESTING_HTTP_PORT: 8888 + +jobs: + build: + + runs-on: ubuntu-20.04 + + strategy: + matrix: + DRUPAL_TESTING_DRUPAL_VERSION: ['~8.9.0', '~9.1.0'] + PHP_VERSION: [ '7.4' ] + + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-node@v1 + with: + node-version: '12.x' + + - uses: shivammathur/setup-php@v2 + with: + coverage: none + php-version: ${{ matrix.PHP_VERSION }} + extensions: Imagick, gd, pdo_mysql + + - name: Start MySql service + run: | + sudo /etc/init.d/mysql start + mysql -uroot -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';" + + - name: Cache composer dependencies + uses: actions/cache@v1 + with: + path: ~/.composer/cache + key: ${{ runner.os }}-composer-cache-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer-cache- + + - name: Get build environment + run: composer global require thunder/drupal-testing + + - name: Prepare the build + run: test-drupal-project prepare_build + env: + DRUPAL_TESTING_DRUPAL_VERSION: ${{ matrix.DRUPAL_TESTING_DRUPAL_VERSION }} + + - name: Build the docroot + run: test-drupal-project build + + - name: Test for deprecations + run: test-drupal-project deprecation + if: ${{ matrix.DRUPAL_TESTING_DRUPAL_VERSION != '~9.1.0' }} + + - name: Install drupal + run: test-drupal-project install + env: + DRUPAL_TESTING_TEST_DEPRECATION: false + + - name: Setup Apache + uses: thunder/apache-shiva-php-action@v1 + with: + php-version: ${{ matrix.PHP_VERSION }} + site-directory: /tmp/test/graphql-views/install/web + http-port: ${{ env.DRUPAL_TESTING_HTTP_PORT }} + + - name: Run the tests + run: test-drupal-project diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8069089..0000000 --- a/.travis.yml +++ /dev/null @@ -1,104 +0,0 @@ -language: php -sudo: false - -php: - - 7.4 - - 7.3 - -services: - - mysql - -env: - global: - - DRUPAL_GRAPHQL=8.x-4.x - - DRUPAL_BUILD_DIR=$TRAVIS_BUILD_DIR/../drupal - - SIMPLETEST_DB=mysql://root:@127.0.0.1/graphql - - TRAVIS=true - matrix: - - DRUPAL_CORE=9.1.x - - DRUPAL_CORE=8.9.x - -matrix: - # Don't wait for the allowed failures to build. - fast_finish: true - include: - - php: 7.3 - env: - - DRUPAL_CORE=9.1.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.3 - env: - - DRUPAL_CORE=9.1.x - # Only run code coverage on the latest php and drupal versions. - - WITH_PHPDBG_COVERAGE=true - -mysql: - database: graphql - username: root - encoding: utf8 - -# Cache composer downloads. -cache: - directories: - - $HOME/.composer - -before_install: - # Disable xdebug. - - phpenv config-rm xdebug.ini - - # Determine the php settings file location. - - if [[ $TRAVIS_PHP_VERSION = hhvm* ]]; - then export PHPINI=/etc/hhvm/php.ini; - else export PHPINI=$HOME/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; - fi - - # Disable the default memory limit. - - echo memory_limit = -1 >> $PHPINI - - # Update composer. - - composer self-update - -install: - # Create the database. - - mysql -e 'create database graphql' - - # Download Drupal 8 core from the Github mirror because it is faster. - - git clone --branch $DRUPAL_CORE --depth 1 https://github.com/drupal/drupal.git $DRUPAL_BUILD_DIR - - git clone --branch $DRUPAL_GRAPHQL --depth 1 https://github.com/drupal-graphql/graphql.git $DRUPAL_BUILD_DIR/modules/graphql - - # Reference the module in the build site. - - ln -s $TRAVIS_BUILD_DIR $DRUPAL_BUILD_DIR/modules/graphql_views - - # Copy the customized phpunit configuration file to the core directory so - # the relative paths are correct. - - cp $DRUPAL_BUILD_DIR/modules/graphql/phpunit.xml.dist $DRUPAL_BUILD_DIR/core/phpunit.xml - - # When running with phpdbg we need to replace all code occurrences that check - # for 'cli' with 'phpdbg'. Some files might be write protected, hence the - # fallback. - - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]]; - then grep -rl 'cli' $DRUPAL_BUILD_DIR/core $DRUPAL_BUILD_DIR/modules | xargs sed -i "s/'cli'/'phpdbg'/g" || true; - fi - - # 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.5 - -script: - # Run the unit tests using phpdbg if the environment variable is 'true'. - - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]]; - then phpdbg -qrr $DRUPAL_BUILD_DIR/vendor/bin/phpunit --configuration $DRUPAL_BUILD_DIR/core/phpunit.xml --coverage-clover $TRAVIS_BUILD_DIR/coverage.xml $TRAVIS_BUILD_DIR; - fi - - # Run the unit tests with standard php otherwise. - - if [[ "$WITH_PHPDBG_COVERAGE" != "true" ]]; - then $DRUPAL_BUILD_DIR/vendor/bin/phpunit --configuration $DRUPAL_BUILD_DIR/core/phpunit.xml $TRAVIS_BUILD_DIR; - fi - -after_success: - - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]]; - then bash <(curl -s https://codecov.io/bash); - fi From 557eb3c77f2aba2c16f26744c67c8de6589b4bbf Mon Sep 17 00:00:00 2001 From: Christian Fritsch <chr.fritsch@gmx.net> Date: Wed, 9 Jun 2021 12:19:41 +0200 Subject: [PATCH 04/10] fix: semantic version --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1b137df..f7fb9dc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - 8.x-[1-9]+.x + - [1-9]+.x paths-ignore: - '**.md' From 548a80c67ce7ac01c53632aa356466da9e7d9a4a Mon Sep 17 00:00:00 2001 From: Christian Fritsch <chr.fritsch@gmx.net> Date: Wed, 9 Jun 2021 12:22:45 +0200 Subject: [PATCH 05/10] fix: schedule --- .github/workflows/test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f7fb9dc..bc62441 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,9 +2,6 @@ name: Run module tests on: pull_request: - branches: - - 8.x-[1-9]+.x - - [1-9]+.x paths-ignore: - '**.md' From 4a58d416c0e8e23c9a09777e9e0d87fd9116b3bb Mon Sep 17 00:00:00 2001 From: Christian Fritsch <chr.fritsch@gmx.net> Date: Wed, 9 Jun 2021 12:26:54 +0200 Subject: [PATCH 06/10] fix: add composer.json --- composer.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..723d9ed --- /dev/null +++ b/composer.json @@ -0,0 +1,8 @@ +{ + "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", + "license": "GPL-2.0+", + "minimum-stability": "dev" +} From ca12b9d5a80f0b2da3446c8ea63dbcd720e8985b Mon Sep 17 00:00:00 2001 From: Christian Fritsch <chr.fritsch@gmx.net> Date: Wed, 9 Jun 2021 12:30:29 +0200 Subject: [PATCH 07/10] fix: add requirement --- composer.json | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 723d9ed..90ce9e5 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,11 @@ { - "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", - "license": "GPL-2.0+", - "minimum-stability": "dev" + "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", + "license": "GPL-2.0+", + "minimum-stability": "dev", + "require": { + "drupal/graphql": "^4.1" + } } From a25327245a2451962af2dd0188425c5ff870aace Mon Sep 17 00:00:00 2001 From: Christian Fritsch <chr.fritsch@gmx.net> Date: Wed, 9 Jun 2021 13:48:47 +0200 Subject: [PATCH 08/10] fix: cleanup --- src/Plugin/GraphQL/DataProducer/Views.php | 28 +++++++++++++------ .../graphql_views_test.module | 8 ++++++ tests/src/Kernel/ContextualViewsTest.php | 28 +------------------ 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/src/Plugin/GraphQL/DataProducer/Views.php b/src/Plugin/GraphQL/DataProducer/Views.php index f445a81..223ccbe 100644 --- a/src/Plugin/GraphQL/DataProducer/Views.php +++ b/src/Plugin/GraphQL/DataProducer/Views.php @@ -92,6 +92,14 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition * Number of items on page. * @param int|null $page * Number of page. + * @param string $sort_by + * Fields to sort by. + * @param string $sort_direction + * Direction to sort. ASC or DESC. + * @param array $filter + * Exposed filters. + * @param \Drupal\graphql\GraphQL\Execution\FieldContext $fieldContext + * Context to set cache on. * * @return array|null * List of entities. @@ -99,7 +107,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ - public function resolve(string $view_id, string $display_id, int $offset = NULL, int $page_size = NULL, int $page = NULL, string $sort_by = NULL, string $sort_direction = NULL, array $filter = [], FieldContext $fieldContext) { + public function resolve(string $view_id, string $display_id, $offset, $page_size, $page, $sort_by, $sort_direction, array $filter, FieldContext $fieldContext) { /** @var \Drupal\views\Entity\View $view */ $view = \Drupal::entityTypeManager()->getStorage('view')->load($view_id); @@ -138,7 +146,6 @@ public function resolve(string $view_id, string $display_id, int $offset = NULL, $result = $executable->render($display_id); - /** @var \Drupal\Core\Cache\CacheableMetadata $cache */ if ($cache = $result['cache']) { $cache->setCacheContexts( @@ -210,17 +217,19 @@ protected function getPagerOffset(DisplayPluginInterface $display) { /** * Retrieves sort and filter arguments from the provided field args. * - * @param $value - * The resolved parent value. - * @param $args - * The array of arguments provided to the field. - * @param $available_filters + * @param string $sort_by + * Fields to sort by. + * @param string $sort_direction + * Direction to sort. ASC or DESC. + * @param array $filter + * Exposed filters. + * @param array $available_filters * The available filters for the configured view. * * @return array * The array of sort and filter arguments to execute the view with. */ - protected function extractExposedInput($sort_by, $sort_direction, $filter, $available_filters) { + protected function extractExposedInput($sort_by, $sort_direction, array $filter, array $available_filters) { // Prepare arguments for use as exposed form input. $input = array_filter([ // Sorting arguments. @@ -238,7 +247,8 @@ protected function extractExposedInput($sort_by, $sort_direction, $filter, $avai $inputKey = $filterRow['expose']['identifier']; if (!isset($filter[$inputKey])) { $input[$inputKey] = $filterRow['value']; - } else { + } + else { $input[$inputKey] = $filter[$inputKey]; } } diff --git a/tests/modules/graphql_views_test/graphql_views_test.module b/tests/modules/graphql_views_test/graphql_views_test.module index 513c30b..54a29fc 100644 --- a/tests/modules/graphql_views_test/graphql_views_test.module +++ b/tests/modules/graphql_views_test/graphql_views_test.module @@ -1,7 +1,15 @@ <?php +/** + * @file + * Hook implementations. + */ + use Drupal\views\ViewExecutable; +/** + * Implements hook_views_pre_build(). + */ function graphql_views_test_views_pre_build(ViewExecutable $view) { $args =& drupal_static('graphql_views_test:view:args', []); $id = $view->storage->id() . ':' . $view->current_display; diff --git a/tests/src/Kernel/ContextualViewsTest.php b/tests/src/Kernel/ContextualViewsTest.php index 34fa2f7..551f2e7 100644 --- a/tests/src/Kernel/ContextualViewsTest.php +++ b/tests/src/Kernel/ContextualViewsTest.php @@ -44,6 +44,7 @@ protected function defaultCacheTags(): array { * Test if view contextual filters are set properly. */ public function testContextualViewArgs() { + $this->markTestSkipped('Not supported right now.'); $test2Node = $this->createNode(['type' => 'test2']); $this->graphQlProcessor()->processQuery( @@ -84,31 +85,4 @@ public function testContextualViewArgs() { ]); } - /** - * Test if view fields are attached to correct types. - */ - public function testContextualViewFields() { - $schema = $this->introspect(); - - $field = 'graphqlTestContextualTitleArgView'; - $this->assertArrayHasKey($field, $schema['types']['Query']['fields']); - $this->assertArrayNotHasKey($field, $schema['types']['Node']['fields']); - $this->assertArrayNotHasKey($field, $schema['types']['NodeTest']['fields']); - - $field = 'graphqlTestContextualNodeView'; - $this->assertArrayHasKey($field, $schema['types']['Query']['fields']); - $this->assertArrayHasKey($field, $schema['types']['Node']['fields']); - $this->assertArrayHasKey($field, $schema['types']['NodeTest']['fields']); - - $field = 'graphqlTestContextualNodetestView'; - $this->assertArrayHasKey($field, $schema['types']['Query']['fields']); - $this->assertArrayNotHasKey($field, $schema['types']['Node']['fields']); - $this->assertArrayHasKey($field, $schema['types']['NodeTest']['fields']); - - $field = 'graphqlTestContextualNodeAndNodetestView'; - $this->assertArrayHasKey($field, $schema['types']['Query']['fields']); - $this->assertArrayHasKey($field, $schema['types']['Node']['fields']); - $this->assertArrayHasKey($field, $schema['types']['NodeTest']['fields']); - } - } From fc21ee63d13ec3831b33e685b93442d19d70b4c2 Mon Sep 17 00:00:00 2001 From: Christian Fritsch <chr.fritsch@gmx.net> Date: Wed, 9 Jun 2021 14:23:12 +0200 Subject: [PATCH 09/10] fix: add readme --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb00c8a --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# GraphQL Views for Drupal + +[](https://github.com/drupal-graphql/graphql-views/actions) + +[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. + +Any `GraphQL` views display will provide a field that will adapt to the views configuration: + +- 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. + +Please also refer to the main [Drupal GraphQL] module for further information. From c671bf0a8188f880a2c565ef7e07016aa30f39d9 Mon Sep 17 00:00:00 2001 From: Christian Fritsch <chr.fritsch@gmx.net> Date: Wed, 9 Jun 2021 15:03:10 +0200 Subject: [PATCH 10/10] fix: remove query name from schema --- config/schema/graphql_views.views.schema.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config/schema/graphql_views.views.schema.yml b/config/schema/graphql_views.views.schema.yml index 3c55ce4..5ef4caf 100644 --- a/config/schema/graphql_views.views.schema.yml +++ b/config/schema/graphql_views.views.schema.yml @@ -1,7 +1,3 @@ views.display.graphql: type: views_display label: 'GraphQL display options' - mapping: - graphql_query_name: - type: string - label: 'GraphQL query name'