├── .scrutinizer.yml
├── .travis.yml
├── README.md
├── composer.json
├── config
└── schema
│ └── graphql_views.views.schema.yml
├── graphql_views.info.yml
├── phpcs.xml.dist
├── src
├── Plugin
│ ├── Deriver
│ │ ├── Enums
│ │ │ └── ViewSortByDeriver.php
│ │ ├── Fields
│ │ │ ├── ViewDeriver.php
│ │ │ ├── ViewResultCountDeriver.php
│ │ │ ├── ViewResultListDeriver.php
│ │ │ └── ViewRowFieldDeriver.php
│ │ ├── InputTypes
│ │ │ ├── ViewContextualFilterInputDeriver.php
│ │ │ └── ViewFilterInputDeriver.php
│ │ ├── Types
│ │ │ ├── ViewResultTypeDeriver.php
│ │ │ └── ViewRowTypeDeriver.php
│ │ └── ViewDeriverBase.php
│ ├── GraphQL
│ │ ├── Enums
│ │ │ ├── ViewSortBy.php
│ │ │ └── ViewSortDirection.php
│ │ ├── Fields
│ │ │ ├── Entity
│ │ │ │ └── Fields
│ │ │ │ │ └── View
│ │ │ │ │ └── ViewDerivative.php
│ │ │ ├── View.php
│ │ │ ├── ViewResultCount.php
│ │ │ ├── ViewResultList.php
│ │ │ └── ViewRowField.php
│ │ ├── InputTypes
│ │ │ ├── ViewContextualFilterInput.php
│ │ │ └── ViewFilterInput.php
│ │ ├── Scalars
│ │ │ └── TypedData
│ │ │ │ ├── ViewsContextualFilterInput.php
│ │ │ │ ├── ViewsFilterInput.php
│ │ │ │ └── ViewsSortByInput.php
│ │ ├── Types
│ │ │ ├── ViewResultType.php
│ │ │ └── ViewRowType.php
│ │ └── UnionTypes
│ │ │ └── ViewResult.php
│ └── views
│ │ ├── display
│ │ └── GraphQL.php
│ │ ├── exposed_form
│ │ └── GraphQL.php
│ │ ├── row
│ │ ├── GraphQLEntityRow.php
│ │ └── GraphQLFieldRow.php
│ │ └── style
│ │ └── GraphQL.php
└── ViewDeriverHelperTrait.php
└── tests
├── modules
└── graphql_views_test
│ ├── config
│ └── install
│ │ ├── views.view.graphql_bundle_test.yml
│ │ └── views.view.graphql_test.yml
│ ├── graphql_views_test.features.yml
│ ├── graphql_views_test.info.yml
│ └── graphql_views_test.module
├── queries
├── contextual.gql
├── paged.gql
├── simple.gql
├── single_bundle_filter.gql
└── sorted.gql
└── src
└── Kernel
├── ContextualViewsTest.php
├── ViewsTest.php
└── ViewsTestBase.php
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | filter:
2 | excluded_paths:
3 | - 'tests/*'
4 |
5 | checks:
6 | php: true
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | sudo: false
3 |
4 | php:
5 | - 7.3
6 | - 7.2
7 | - 7.1
8 | - 7.0
9 |
10 | services:
11 | - mysql
12 |
13 | env:
14 | global:
15 | - DRUPAL_GRAPHQL=8.x-3.x
16 | - DRUPAL_BUILD_DIR=$TRAVIS_BUILD_DIR/../drupal
17 | - SIMPLETEST_DB=mysql://root:@127.0.0.1/graphql
18 | - TRAVIS=true
19 | matrix:
20 | - DRUPAL_CORE=8.7.x
21 | - DRUPAL_CORE=8.8.x
22 |
23 | matrix:
24 | # Don't wait for the allowed failures to build.
25 | fast_finish: true
26 | include:
27 | - php: 7.3
28 | env:
29 | - DRUPAL_CORE=8.7.x
30 | # Only run code coverage on the latest php and drupal versions.
31 | - WITH_PHPDBG_COVERAGE=true
32 | allow_failures:
33 | # Allow the code coverage report to fail.
34 | - php: 7.3
35 | env:
36 | - DRUPAL_CORE=8.7.x
37 | # Only run code coverage on the latest php and drupal versions.
38 | - WITH_PHPDBG_COVERAGE=true
39 |
40 | mysql:
41 | database: graphql
42 | username: root
43 | encoding: utf8
44 |
45 | # Cache composer downloads.
46 | cache:
47 | directories:
48 | - $HOME/.composer
49 |
50 | before_install:
51 | # Disable xdebug.
52 | - phpenv config-rm xdebug.ini
53 |
54 | # Determine the php settings file location.
55 | - if [[ $TRAVIS_PHP_VERSION = hhvm* ]];
56 | then export PHPINI=/etc/hhvm/php.ini;
57 | else export PHPINI=$HOME/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini;
58 | fi
59 |
60 | # Disable the default memory limit.
61 | - echo memory_limit = -1 >> $PHPINI
62 |
63 | # Update composer.
64 | - composer self-update
65 |
66 | install:
67 | # Create the database.
68 | - mysql -e 'create database graphql'
69 |
70 | # Download Drupal 8 core from the Github mirror because it is faster.
71 | - git clone --branch $DRUPAL_CORE --depth 1 https://github.com/drupal/drupal.git $DRUPAL_BUILD_DIR
72 | - git clone --branch $DRUPAL_GRAPHQL --depth 1 https://github.com/drupal-graphql/graphql.git $DRUPAL_BUILD_DIR/modules/graphql
73 |
74 | # Reference the module in the build site.
75 | - ln -s $TRAVIS_BUILD_DIR $DRUPAL_BUILD_DIR/modules/graphql_views
76 |
77 | # Copy the customized phpunit configuration file to the core directory so
78 | # the relative paths are correct.
79 | - cp $DRUPAL_BUILD_DIR/modules/graphql/phpunit.xml.dist $DRUPAL_BUILD_DIR/core/phpunit.xml
80 |
81 | # When running with phpdbg we need to replace all code occurrences that check
82 | # for 'cli' with 'phpdbg'. Some files might be write protected, hence the
83 | # fallback.
84 | - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]];
85 | then grep -rl 'cli' $DRUPAL_BUILD_DIR/core $DRUPAL_BUILD_DIR/modules | xargs sed -i "s/'cli'/'phpdbg'/g" || true;
86 | fi
87 |
88 | # Bring in the module dependencies without requiring a merge plugin. The
89 | # require also triggers a full 'composer install'.
90 | - composer --working-dir=$DRUPAL_BUILD_DIR require webonyx/graphql-php:^0.12.5
91 |
92 | # For Drupal < 8.8 we have to manually upgrade zend-stdlib to avoid PHP 7.3
93 | # incompatibilities.
94 | - if [[ "$DRUPAL_CORE" = "8.6.x" || "$DRUPAL_CORE" = "8.7.x" ]];
95 | then composer --working-dir=$DRUPAL_BUILD_DIR require zendframework/zend-stdlib:3.2.1;
96 | fi
97 |
98 | # For Drupal < 8.8 we have to manually upgrade phpunit to avoid PHP 7.3
99 | # incompatibilities.
100 | - if [[ "$DRUPAL_CORE" = "8.6.x" || "$DRUPAL_CORE" = "8.7.x" ]];
101 | then composer --working-dir=$DRUPAL_BUILD_DIR run-script drupal-phpunit-upgrade;
102 | fi
103 |
104 | script:
105 | # Run the unit tests using phpdbg if the environment variable is 'true'.
106 | - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]];
107 | 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;
108 | fi
109 |
110 | # Run the unit tests with standard php otherwise.
111 | - if [[ "$WITH_PHPDBG_COVERAGE" != "true" ]];
112 | then $DRUPAL_BUILD_DIR/vendor/bin/phpunit --configuration $DRUPAL_BUILD_DIR/core/phpunit.xml $TRAVIS_BUILD_DIR;
113 | fi
114 |
115 | after_success:
116 | - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]];
117 | then bash <(curl -s https://codecov.io/bash);
118 | fi
119 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GraphQL Views for Drupal
2 |
3 | [](https://travis-ci.org/drupal-graphql/graphql-views)
4 | [](https://codecov.io/gh/drupal-graphql/graphql-views)
5 | [](https://scrutinizer-ci.com/g/drupal-graphql/graphql-views/?branch=8.x-1.x)
6 |
7 | [Drupal GraphQL]: https://github.com/drupal-graphql/graphql
8 |
9 | With `graphql_views` enabled a `GraphQL` views display can be added to any view in the system.
10 |
11 | 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.
12 |
13 | Any `GraphQL` views display will provide a field that will adapt to the views configuration:
14 |
15 | - The fields name will be composed of the views and displays machine names or configured manually.
16 | - 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.
17 | - Any exposed filters will be added to the `filters` input type that can be used to pass filter values into the view.
18 | - Any contextual filters will be added to the `contextual_filters` input type.
19 | - 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.
20 |
21 | Read more on:
22 | - https://www.amazeelabs.com/en/blog/graphql-drupalers-part-4-fetching-entities
23 | - https://www.amazeelabs.com/en/blog/drupal-graphql-batteries-included
24 |
25 | Please also refer to the main [Drupal GraphQL] module for further information.
26 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "drupal/graphql_views",
3 | "type": "drupal-module",
4 | "description": "Exposes your Drupal Views data model through a GraphQL schema.",
5 | "homepage": "http://drupal.org/project/graphql_views",
6 | "license": "GPL-2.0+",
7 | "minimum-stability": "dev"
8 | }
9 |
--------------------------------------------------------------------------------
/config/schema/graphql_views.views.schema.yml:
--------------------------------------------------------------------------------
1 | views.display.graphql:
2 | type: views_display
3 | label: 'GraphQL display options'
4 | mapping:
5 | graphql_query_name:
6 | type: string
7 | label: 'GraphQL query name'
8 |
--------------------------------------------------------------------------------
/graphql_views.info.yml:
--------------------------------------------------------------------------------
1 | name: GraphQL Views
2 | type: module
3 | description: 'Adds support for views.'
4 | package: GraphQL
5 | core: 8.x
6 | core_version_requirement: ^8 || ^9
7 | dependencies:
8 | - graphql:graphql_core
9 | - drupal:views
10 | - drupal:system (>=8.4)
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 | Default PHP CodeSniffer configuration for GraphQL.
4 | .
5 |
6 |
7 |
8 |
9 |
10 | src/Annotation
11 | src/Core/Annotation
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Plugin/Deriver/Enums/ViewSortByDeriver.php:
--------------------------------------------------------------------------------
1 | entityTypeManager->hasDefinition('view')) {
16 | $viewStorage = $this->entityTypeManager->getStorage('view');
17 |
18 | foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) {
19 | /** @var \Drupal\views\ViewEntityInterface $view */
20 | $view = $viewStorage->load($viewId);
21 | if (!$type = $this->getRowResolveType($view, $displayId)) {
22 | continue;
23 | }
24 |
25 | /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */
26 | $display = $this->getViewDisplay($view, $displayId);
27 | $sorts = array_filter($display->getOption('sorts') ?: [], function ($sort) {
28 | return $sort['exposed'];
29 | });
30 | $sorts = array_reduce($sorts, function ($carry, $sort) {
31 | $carry[strtoupper($sort['id'])] = [
32 | 'value' => $sort['id'],
33 | 'description' => $sort['expose']['label'],
34 | ];
35 | return $carry;
36 | }, []);
37 |
38 | if (!empty($sorts)) {
39 | $id = implode('-', [$viewId, $displayId, 'view']);
40 | $this->derivatives["$viewId-$displayId"] = [
41 | 'name' => StringHelper::camelCase($id, 'sort', 'by'),
42 | 'values' => $sorts,
43 | ] + $basePluginDefinition;
44 | }
45 | }
46 | }
47 |
48 | return parent::getDerivativeDefinitions($basePluginDefinition);
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/Plugin/Deriver/Fields/ViewDeriver.php:
--------------------------------------------------------------------------------
1 | entityTypeManager->hasDefinition('view')) {
19 | $viewStorage = $this->entityTypeManager->getStorage('view');
20 |
21 | foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) {
22 | /** @var \Drupal\views\ViewEntityInterface $view */
23 | $view = $viewStorage->load($viewId);
24 | if (!$this->getRowResolveType($view, $displayId)) {
25 | continue;
26 | }
27 |
28 | /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */
29 | $display = $this->getViewDisplay($view, $displayId);
30 |
31 | $id = implode('-', [$viewId, $displayId, 'view']);
32 | $info = $this->getArgumentsInfo($display->getOption('arguments') ?: []);
33 | $arguments = [];
34 | $arguments += $this->getContextualArguments($info, $id);
35 | $arguments += $this->getPagerArguments($display);
36 | $arguments += $this->getSortArguments($display, $id);
37 | $arguments += $this->getFilterArguments($display, $id);
38 | $types = $this->getTypes($info);
39 |
40 | $this->derivatives[$id] = [
41 | 'id' => $id,
42 | 'name' => $display->getGraphQLQueryName(),
43 | 'type' => $display->getGraphQLResultName(),
44 | 'parents' => $types,
45 | 'arguments' => $arguments,
46 | 'view' => $viewId,
47 | 'display' => $displayId,
48 | 'paged' => $this->isPaged($display),
49 | 'arguments_info' => $info,
50 | ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition;
51 | }
52 | }
53 |
54 | return parent::getDerivativeDefinitions($basePluginDefinition);
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/Plugin/Deriver/Fields/ViewResultCountDeriver.php:
--------------------------------------------------------------------------------
1 | entityTypeManager->hasDefinition('view')) {
18 | $viewStorage = $this->entityTypeManager->getStorage('view');
19 |
20 | foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) {
21 | /** @var \Drupal\views\ViewEntityInterface $view */
22 | $view = $viewStorage->load($viewId);
23 | /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */
24 | $display = $this->getViewDisplay($view, $displayId);
25 | if (!$this->isPaged($display)) {
26 | continue;
27 | }
28 |
29 | if (!$this->getRowResolveType($view, $displayId)) {
30 | continue;
31 | }
32 |
33 | $id = implode('-', [$viewId, $displayId, 'result', 'count']);
34 | $this->derivatives[$id] = [
35 | 'id' => $id,
36 | 'type' => 'Int',
37 | 'parents' => [$display->getGraphQLResultName()],
38 | 'view' => $viewId,
39 | 'display' => $displayId,
40 | ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition;
41 | }
42 | }
43 |
44 | return parent::getDerivativeDefinitions($basePluginDefinition);
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/Plugin/Deriver/Fields/ViewResultListDeriver.php:
--------------------------------------------------------------------------------
1 | entityTypeManager->hasDefinition('view')) {
19 | $viewStorage = $this->entityTypeManager->getStorage('view');
20 |
21 | foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) {
22 | /** @var \Drupal\views\ViewEntityInterface $view */
23 | $view = $viewStorage->load($viewId);
24 | if (!$type = $this->getRowResolveType($view, $displayId)) {
25 | continue;
26 | }
27 |
28 | /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */
29 | $display = $this->getViewDisplay($view, $displayId);
30 |
31 | $id = implode('-', [$viewId, $displayId, 'result', 'list']);
32 | $style = $this->getViewStyle($view, $displayId);
33 | $this->derivatives[$id] = [
34 | 'id' => $id,
35 | 'type' => StringHelper::listType($type),
36 | 'parents' => [$display->getGraphQLResultName()],
37 | 'view' => $viewId,
38 | 'display' => $displayId,
39 | 'uses_fields' => $style->usesFields(),
40 | ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition;
41 | }
42 | }
43 |
44 | return parent::getDerivativeDefinitions($basePluginDefinition);
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/Plugin/Deriver/Fields/ViewRowFieldDeriver.php:
--------------------------------------------------------------------------------
1 | entityTypeManager->hasDefinition('view')) {
19 | $viewStorage = $this->entityTypeManager->getStorage('view');
20 |
21 | foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) {
22 | /** @var \Drupal\views\ViewEntityInterface $view */
23 | $view = $viewStorage->load($viewId);
24 | /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */
25 | $display = $this->getViewDisplay($view, $displayId);
26 | $rowPlugin = $display->getPlugin('row');
27 |
28 | // This deriver only supports our custom field row plugin.
29 | if (!$rowPlugin instanceof GraphQLFieldRow) {
30 | continue;
31 | }
32 |
33 | foreach ($display->getHandlers('field') as $name => $field) {
34 | $id = implode('-', [$viewId, $displayId, 'field', $name]);
35 | $alias = $rowPlugin->getFieldKeyAlias($name);
36 | $type = $rowPlugin->getFieldType($name);
37 |
38 | $this->derivatives[$id] = [
39 | 'id' => $id,
40 | 'name' => $alias,
41 | 'type' => $type,
42 | 'parents' => [$display->getGraphQLRowName()],
43 | 'view' => $viewId,
44 | 'display' => $displayId,
45 | 'field' => $alias,
46 | ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition;
47 | }
48 | }
49 | }
50 |
51 | return parent::getDerivativeDefinitions($basePluginDefinition);
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/Plugin/Deriver/InputTypes/ViewContextualFilterInputDeriver.php:
--------------------------------------------------------------------------------
1 | entityTypeManager->hasDefinition('view')) {
20 | $viewStorage = $this->entityTypeManager->getStorage('view');
21 |
22 | foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) {
23 | /** @var \Drupal\views\ViewEntityInterface $view */
24 | $view = $viewStorage->load($viewId);
25 | if (!$this->getRowResolveType($view, $displayId)) {
26 | continue;
27 | }
28 |
29 | $display = $this->getViewDisplay($view, $displayId);
30 | $argumentsInfo = $this->getArgumentsInfo($display->getOption('arguments') ?: []);
31 | if (!empty($argumentsInfo)) {
32 | $id = implode('_', [
33 | $viewId, $displayId, 'view', 'contextual', 'filter', 'input',
34 | ]);
35 |
36 | $this->derivatives[$id] = [
37 | 'id' => $id,
38 | 'name' => StringHelper::camelCase($viewId, $displayId, 'view', 'contextual', 'filter', 'input'),
39 | 'fields' => array_fill_keys(array_keys($argumentsInfo), [
40 | 'type' => 'String',
41 | ]),
42 | 'view' => $viewId,
43 | 'display' => $displayId,
44 | ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition;
45 | }
46 | }
47 | }
48 |
49 | return parent::getDerivativeDefinitions($basePluginDefinition);
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/Plugin/Deriver/InputTypes/ViewFilterInputDeriver.php:
--------------------------------------------------------------------------------
1 | entityTypeManager->hasDefinition('view')) {
20 | $viewStorage = $this->entityTypeManager->getStorage('view');
21 |
22 | foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) {
23 | /** @var \Drupal\views\ViewEntityInterface $view */
24 | $view = $viewStorage->load($viewId);
25 | if (!$this->getRowResolveType($view, $displayId)) {
26 | continue;
27 | }
28 |
29 | /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */
30 | $display = $this->getViewDisplay($view, $displayId);
31 | $id = implode('_', [$viewId, $displayId, 'view', 'filter', 'input']);
32 |
33 | // Re-key filters by filter identifier.
34 | $filters = array_reduce(array_filter($display->getOption('filters') ?: [], function($filter) {
35 | return array_key_exists('exposed', $filter) && $filter['exposed'];
36 | }), function($carry, $current) {
37 | return $carry + [
38 | $current['expose']['identifier'] => $current,
39 | ];
40 | }, []);
41 |
42 | // If there are no exposed filters, don't create the derivative.
43 | if (empty($filters)) {
44 | continue;
45 | }
46 |
47 | $fields = array_map(function($filter) use ($basePluginDefinition) {
48 | if ($this->isGenericInputFilter($filter)) {
49 | return $this->createGenericInputFilterDefinition($filter, $basePluginDefinition);
50 | }
51 |
52 | return [
53 | 'type' => $filter['expose']['multiple'] ? StringHelper::listType('String') : 'String',
54 | ];
55 | }, $filters);
56 |
57 | $this->derivatives[$id] = [
58 | 'id' => $id,
59 | 'name' => $display->getGraphQLFilterInputName(),
60 | 'fields' => $fields,
61 | 'view' => $viewId,
62 | 'display' => $displayId,
63 | ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition;
64 | }
65 |
66 | }
67 | return parent::getDerivativeDefinitions($basePluginDefinition);
68 | }
69 |
70 | /**
71 | * Checks if a filter definition is a generic input filter.
72 | *
73 | * @param mixed $filter
74 | * $filter['value'] = [];
75 | * $filter['value'] = [
76 | * "text",
77 | * "test"
78 | * ];
79 | * $filter['value'] = [
80 | * 'distance' => 10,
81 | * 'distance2' => 30,
82 | * ...
83 | * ];
84 | * @return bool
85 | */
86 | public function isGenericInputFilter($filter) {
87 | if (!is_array($filter['value']) || count($filter['value']) == 0) {
88 | return false;
89 | }
90 |
91 | $firstKey = array_keys($filter['value'])[0];
92 | return is_string($firstKey);
93 | }
94 |
95 | /**
96 | * Creates a definition for a generic input filter.
97 | *
98 | * @param mixed $filter
99 | * $filter['value'] = [];
100 | * $filter['value'] = [
101 | * "text",
102 | * "test"
103 | * ];
104 | * $filter['value'] = [
105 | * 'distance' => 10,
106 | * 'distance2' => 30,
107 | * ...
108 | * ];
109 | * @param mixed $basePluginDefinition
110 | * @return array
111 | */
112 | public function createGenericInputFilterDefinition($filter, $basePluginDefinition) {
113 | $filterId = $filter['expose']['identifier'];
114 |
115 | $id = implode('_', [
116 | $filter['expose']['multiple'] ? $filterId : $filterId . '_multi',
117 | 'view',
118 | 'filter',
119 | 'input',
120 | ]);
121 |
122 | $fields = [];
123 | foreach ($filter['value'] as $fieldKey => $fieldDefaultValue) {
124 | $fields[$fieldKey] = [
125 | 'type' => 'String',
126 | ];
127 | }
128 |
129 | $genericInputFilter = [
130 | 'id' => $id,
131 | 'name' => StringHelper::camelCase($id),
132 | 'fields' => $fields,
133 | ] + $basePluginDefinition;
134 |
135 | $this->derivatives[$id] = $genericInputFilter;
136 |
137 | return [
138 | 'type' => $filter['expose']['multiple'] ? StringHelper::listType($genericInputFilter['name']) : $genericInputFilter['name'],
139 | ];
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/Plugin/Deriver/Types/ViewResultTypeDeriver.php:
--------------------------------------------------------------------------------
1 | entityTypeManager->hasDefinition('view')) {
18 | $viewStorage = $this->entityTypeManager->getStorage('view');
19 |
20 | foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) {
21 | /** @var \Drupal\views\ViewEntityInterface $view */
22 | $view = $viewStorage->load($viewId);
23 | if (!$this->getRowResolveType($view, $displayId)) {
24 | continue;
25 | }
26 |
27 | /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */
28 | $display = $this->getViewDisplay($view, $displayId);
29 |
30 | $id = implode('-', [$viewId, $displayId, 'result']);
31 | $this->derivatives[$id] = [
32 | 'id' => $id,
33 | 'name' => $display->getGraphQLResultName(),
34 | 'view' => $viewId,
35 | 'display' => $displayId,
36 | ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition;
37 | }
38 | }
39 |
40 | return parent::getDerivativeDefinitions($basePluginDefinition);
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/Plugin/Deriver/Types/ViewRowTypeDeriver.php:
--------------------------------------------------------------------------------
1 | entityTypeManager->hasDefinition('view')) {
18 | $viewStorage = $this->entityTypeManager->getStorage('view');
19 |
20 | foreach (Views::getApplicableViews('graphql_display') as list($viewId, $displayId)) {
21 | /** @var \Drupal\views\ViewEntityInterface $view */
22 | $view = $viewStorage->load($viewId);
23 | if (!$this->getRowResolveType($view, $displayId)) {
24 | continue;
25 | }
26 |
27 | $style = $this->getViewStyle($view, $displayId);
28 | // This deriver only supports style plugins that use fields.
29 | if (!$style->usesFields()) {
30 | continue;
31 | }
32 |
33 | /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */
34 | $display = $this->getViewDisplay($view, $displayId);
35 |
36 | $id = implode('-', [$viewId, $displayId, 'row']);
37 | $this->derivatives[$id] = [
38 | 'id' => $id,
39 | 'name' => $display->getGraphQLRowName(),
40 | 'view' => $viewId,
41 | 'display' => $displayId,
42 | ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition;
43 | }
44 | }
45 |
46 | return parent::getDerivativeDefinitions($basePluginDefinition);
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/Plugin/Deriver/ViewDeriverBase.php:
--------------------------------------------------------------------------------
1 | get('entity_type.manager'),
52 | $container->get('plugin.manager.graphql.interface')
53 | );
54 | }
55 |
56 | /**
57 | * Creates a ViewDeriver object.
58 | *
59 | * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
60 | * An entity type manager instance.
61 | * @param \Drupal\Component\Plugin\PluginManagerInterface $interfacePluginManager
62 | * The plugin manager for graphql interfaces.
63 | */
64 | public function __construct(
65 | EntityTypeManagerInterface $entityTypeManager,
66 | PluginManagerInterface $interfacePluginManager
67 | ) {
68 | $this->interfacePluginManager = $interfacePluginManager;
69 | $this->entityTypeManager = $entityTypeManager;
70 | }
71 |
72 | /**
73 | * Retrieves the entity type id of an entity by its base or data table.
74 | *
75 | * @param string $table
76 | * The base or data table of an entity.
77 | *
78 | * @return string
79 | * The id of the entity type that the given base table belongs to.
80 | */
81 | protected function getEntityTypeByTable($table) {
82 | if (!isset($this->dataTables)) {
83 | $this->dataTables = [];
84 |
85 | foreach ($this->entityTypeManager->getDefinitions() as $entityTypeId => $entityType) {
86 | if ($dataTable = $entityType->getDataTable()) {
87 | $this->dataTables[$dataTable] = $entityType->id();
88 | }
89 | if ($baseTable = $entityType->getBaseTable()) {
90 | $this->dataTables[$baseTable] = $entityType->id();
91 | }
92 | }
93 | }
94 |
95 | return !empty($this->dataTables[$table]) ? $this->dataTables[$table] : NULL;
96 | }
97 |
98 | /**
99 | * Retrieves the type the view's rows resolve to.
100 | *
101 | * @param \Drupal\views\ViewEntityInterface $view
102 | * The view entity.
103 | * @param string $displayId
104 | * Interface plugin manager.
105 | *
106 | * @return null|string
107 | * The name of the type or NULL if the type could not be derived.
108 | */
109 | protected function getRowResolveType(ViewEntityInterface $view, $displayId) {
110 | return $this->traitGetRowResolveType($view, $displayId, $this->interfacePluginManager);
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/src/Plugin/GraphQL/Enums/ViewSortBy.php:
--------------------------------------------------------------------------------
1 | [
22 | 'value' => 'ASC',
23 | 'description' => 'Sort in ascending order.',
24 | ],
25 | 'DESC' => [
26 | 'value' => 'DESC',
27 | 'description' => 'Sort in descending order.',
28 | ],
29 | ];
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/Plugin/GraphQL/Fields/Entity/Fields/View/ViewDerivative.php:
--------------------------------------------------------------------------------
1 | getValue();
64 | $this->pluginDefinition['view'] = $values['target_id'];
65 | $this->pluginDefinition['display'] = $values['display_id'];
66 | $view = EntityView::load($values['target_id']);
67 | $display = $this->getViewDisplay($view, $values['display_id']);
68 | $this->pluginDefinition['paged'] = $this->isPaged($display);
69 | $this->pluginDefinition['arguments_info'] = $this->getArgumentsInfo($display->getOption('arguments') ?: []);
70 | $this->pluginDefinition = array_merge($this->pluginDefinition, $this->getCacheMetadataDefinition($view, $display));
71 | $this->setOverridenViewDefaults($value, $args);
72 | $this->setViewDefaultValues($display, $args);
73 | return parent::resolveValues($value, $args, $context, $info);
74 | }
75 |
76 | /**
77 | * Get configuration values from views reference field.
78 | *
79 | * @param mixed $value
80 | * The current object value.
81 | *
82 | * @return array|mixed
83 | * Return unserialized data.
84 | */
85 | protected function getViewReferenceConfiguration($value) {
86 | $values = $value->getValue();
87 | return isset($values['data']) ? unserialize($values['data']) : [];
88 | }
89 |
90 | /**
91 | * Set default display settings.
92 | *
93 | * @param mixed $value
94 | * The current object value.
95 | * @param array $args
96 | * Arguments where the default view settings needs to be added.
97 | */
98 | protected function setOverridenViewDefaults($value, array &$args) {
99 | $viewReferenceConfiguration = $this->getViewReferenceConfiguration($value);
100 | if (!empty($viewReferenceConfiguration['pager'])) {
101 | $this->pluginDefinition['paged'] = in_array($viewReferenceConfiguration['pager'], [
102 | 'full',
103 | 'mini',
104 | ]);
105 | }
106 |
107 | if (!isset($args['pageSize']) && !empty($viewReferenceConfiguration['limit'])) {
108 | $args['pageSize'] = $viewReferenceConfiguration['limit'];
109 | }
110 |
111 | if (!isset($args['offset']) && !empty($viewReferenceConfiguration['offset'])) {
112 | $args['offset'] = $viewReferenceConfiguration['offset'];
113 | }
114 |
115 | /* Expected format: {"contextualFilter": {"key": "value","keyN": "valueN"}} */
116 | if (!isset($args['contextualFilter']) && !empty($viewReferenceConfiguration['argument'])) {
117 | $argument = json_decode($viewReferenceConfiguration['argument'], TRUE);
118 | if (isset($argument['contextualFilter']) && !empty($argument['contextualFilter'])) {
119 | $args['contextualFilter'] = $argument['contextualFilter'];
120 | }
121 | }
122 | }
123 |
124 | /**
125 | * Set default display settings.
126 | *
127 | * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display
128 | * The display configuration.
129 | * @param array $args
130 | * Arguments where the default view settings needs to be added.
131 | */
132 | protected function setViewDefaultValues(DisplayPluginInterface $display, array &$args) {
133 | if (!isset($args['pageSize']) && $this->pluginDefinition['paged']) {
134 | $args['pageSize'] = $this->getPagerLimit($display);
135 | }
136 | if (!isset($args['page']) && $this->pluginDefinition['paged']) {
137 | $args['page'] = $this->getPagerOffset($display);
138 | }
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/src/Plugin/GraphQL/Fields/View.php:
--------------------------------------------------------------------------------
1 | entityTypeManager = $entityTypeManager;
45 | parent::__construct($configuration, $pluginId, $pluginDefinition);
46 | }
47 |
48 | /**
49 | * {@inheritdoc}
50 | */
51 | public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) {
52 | return new static(
53 | $configuration,
54 | $pluginId,
55 | $pluginDefinition,
56 | $container->get('entity_type.manager')
57 | );
58 | }
59 |
60 | /**
61 | * {@inheritdoc}
62 | */
63 | public function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) {
64 | $storage = $this->entityTypeManager->getStorage('view');
65 | $definition = $this->getPluginDefinition();
66 |
67 | /** @var \Drupal\views\Entity\View $view */
68 | if ($view = $storage->load($definition['view'])) {
69 | $executable = $view->getExecutable();
70 | $executable->setDisplay($definition['display']);
71 |
72 | // Set view contextual filters.
73 | /* @see \Drupal\graphql_views\ViewDeriverHelperTrait::getArgumentsInfo() */
74 | if (!empty($definition['arguments_info'])) {
75 | $arguments = $this->extractContextualFilters($value, $args);
76 | $executable->setArguments($arguments);
77 | }
78 |
79 | $filters = $executable->getDisplay()->getOption('filters');;
80 | $input = $this->extractExposedInput($value, $args, $filters);
81 | $executable->setExposedInput($input);
82 |
83 | // This is a workaround for the Taxonomy Term filter which requires a full
84 | // exposed form to be sent OR the display being an attachment to just
85 | // accept input values.
86 | $executable->is_attachment = TRUE;
87 | $executable->exposed_raw_input = $input;
88 |
89 | if (!empty($definition['paged'])) {
90 | // Set paging parameters.
91 | $executable->setItemsPerPage($args['pageSize']);
92 | $executable->setCurrentPage($args['page']);
93 | }
94 |
95 | if (isset($args['offset']) && !empty($args['offset'])) {
96 | $executable->setOffset($args['offset']);
97 | }
98 |
99 | $result = $executable->render($definition['display']);
100 | /** @var \Drupal\Core\Cache\CacheableMetadata $cache */
101 | if ($cache = $result['cache']) {
102 | $cache->setCacheContexts(
103 | array_filter($cache->getCacheContexts(), function ($context) {
104 | // Don't emit the url cache contexts.
105 | return $context !== 'url' && strpos($context, 'url.') !== 0;
106 | })
107 | );
108 | }
109 | yield $result;
110 | }
111 | }
112 |
113 | /**
114 | * {@inheritdoc}
115 | */
116 | protected function getCacheDependencies(array $result, $value, array $args, ResolveContext $context, ResolveInfo $info) {
117 | return array_map(function ($item) {
118 | return $item['cache'];
119 | }, $result);
120 | }
121 |
122 | /**
123 | * Retrieves the contextual filter argument from the parent value or args.
124 | *
125 | * @param $value
126 | * The resolved parent value.
127 | * @param $args
128 | * The arguments provided to the field.
129 | *
130 | * @return array
131 | * An array of arguments containing the contextual filter value from the
132 | * parent or provided args if any.
133 | */
134 | protected function extractContextualFilters($value, $args) {
135 | $definition = $this->getPluginDefinition();
136 | $arguments = [];
137 |
138 | foreach ($definition['arguments_info'] as $argumentId => $argumentInfo) {
139 | if (isset($args['contextualFilter'][$argumentId])) {
140 | $arguments[$argumentInfo['index']] = $args['contextualFilter'][$argumentId];
141 | }
142 | elseif (
143 | $value instanceof EntityInterface &&
144 | $value->getEntityTypeId() === $argumentInfo['entity_type'] &&
145 | (empty($argumentInfo['bundles']) ||
146 | in_array($value->bundle(), $argumentInfo['bundles'], TRUE))
147 | ) {
148 | $arguments[$argumentInfo['index']] = $value->id();
149 | }
150 | else {
151 | $arguments[$argumentInfo['index']] = NULL;
152 | }
153 | }
154 |
155 | return $arguments;
156 | }
157 |
158 | /**
159 | * Retrieves sort and filter arguments from the provided field args.
160 | *
161 | * @param $value
162 | * The resolved parent value.
163 | * @param $args
164 | * The array of arguments provided to the field.
165 | * @param $filters
166 | * The available filters for the configured view.
167 | *
168 | * @return array
169 | * The array of sort and filter arguments to execute the view with.
170 | */
171 | protected function extractExposedInput($value, $args, $filters) {
172 | // Prepare arguments for use as exposed form input.
173 | $input = array_filter([
174 | // Sorting arguments.
175 | 'sort_by' => isset($args['sortBy']) ? $args['sortBy'] : NULL,
176 | 'sort_order' => isset($args['sortDirection']) ? $args['sortDirection'] : NULL,
177 | ]);
178 |
179 | // If some filters are missing from the input, set them to an empty string
180 | // explicitly. Otherwise views module generates "Undefined index" notice.
181 | foreach ($filters as $filterKey => $filterRow) {
182 | if (!isset($filterRow['expose']['identifier'])) {
183 | continue;
184 | }
185 |
186 | $inputKey = $filterRow['expose']['identifier'];
187 | if (!isset($args['filter'][$inputKey])) {
188 | $input[$inputKey] = $filterRow['value'];
189 | } else {
190 | $input[$inputKey] = $args['filter'][$inputKey];
191 | }
192 | }
193 |
194 | return $input;
195 | }
196 |
197 | }
198 |
--------------------------------------------------------------------------------
/src/Plugin/GraphQL/Fields/ViewResultCount.php:
--------------------------------------------------------------------------------
1 | total_rows);
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/Plugin/GraphQL/Fields/ViewResultList.php:
--------------------------------------------------------------------------------
1 | getPluginDefinition();
26 | if (isset($value[$definition['field']])) {
27 | yield $value[$definition['field']];
28 | }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/Plugin/GraphQL/InputTypes/ViewContextualFilterInput.php:
--------------------------------------------------------------------------------
1 | pluginDefinition['view'] === $view->id() && $this->pluginDefinition['display'] == $view->current_display) {
29 | return TRUE;
30 | }
31 | }
32 |
33 | return parent::applies($object, $context, $info);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/Plugin/GraphQL/Types/ViewRowType.php:
--------------------------------------------------------------------------------
1 | ''];
101 | return $options;
102 | }
103 |
104 | /**
105 | * Get the user defined query name or the default one.
106 | *
107 | * @return string
108 | * Query name.
109 | */
110 | public function getGraphQLQueryName() {
111 | return $this->getGraphQLName();
112 | }
113 |
114 | /**
115 | * Gets the result name.
116 | *
117 | * @return string
118 | * Result name.
119 | */
120 | public function getGraphQLResultName() {
121 | return $this->getGraphQLName('result', TRUE);
122 | }
123 |
124 | /**
125 | * Gets the row name.
126 | *
127 | * @return string
128 | * Row name.
129 | */
130 | public function getGraphQLRowName() {
131 | return $this->getGraphQLName('row', TRUE);
132 | }
133 |
134 | /**
135 | * Gets the filter input name..
136 | *
137 | * @return string
138 | * Result name.
139 | */
140 | public function getGraphQLFilterInputName() {
141 | return $this->getGraphQLName('filter_input', TRUE);
142 | }
143 |
144 | /**
145 | * Gets the contextual filter input name.
146 | *
147 | * @return string
148 | * Result name.
149 | */
150 | public function getGraphQLContextualFilterInputName() {
151 | return $this->getGraphQLName('contextual_filter_input', TRUE);
152 | }
153 |
154 | /**
155 | * Returns the formatted name.
156 | *
157 | * @param string|null $suffix
158 | * Id suffix, eg. row, result.
159 | * @param bool $type
160 | * Whether to use camel- or snake case. Uses camel case if TRUE. Defaults to
161 | * FALSE.
162 | *
163 | * @return string The id.
164 | * The id.
165 | */
166 | public function getGraphQLName($suffix = NULL, $type = FALSE) {
167 | $queryName = strip_tags($this->getOption('graphql_query_name'));
168 |
169 | if (empty($queryName)) {
170 | $viewId = $this->view->id();
171 | $displayId = $this->display['id'];
172 | $parts = [$viewId, $displayId, 'view', $suffix];
173 | return $type ? call_user_func_array([StringHelper::class, 'camelCase'], $parts) : call_user_func_array([StringHelper::class, 'propCase'], $parts);
174 | }
175 |
176 | $parts = array_filter([$queryName, $suffix]);
177 | return $type ? call_user_func_array([StringHelper::class, 'camelCase'], $parts) : call_user_func_array([StringHelper::class, 'propCase'], $parts);
178 | }
179 |
180 | /**
181 | * {@inheritdoc}
182 | */
183 | public function optionsSummary(&$categories, &$options) {
184 | parent::optionsSummary($categories, $options);
185 |
186 | unset($categories['title']);
187 | unset($categories['pager'], $categories['exposed'], $categories['access']);
188 |
189 | unset($options['show_admin_links'], $options['analyze-theme'], $options['link_display']);
190 | unset($options['show_admin_links'], $options['analyze-theme'], $options['link_display']);
191 |
192 | unset($options['title'], $options['access']);
193 | unset($options['exposed_block'], $options['css_class']);
194 | unset($options['query'], $options['group_by']);
195 |
196 | $categories['graphql'] = [
197 | 'title' => $this->t('GraphQL'),
198 | 'column' => 'second',
199 | 'build' => [
200 | '#weight' => -10,
201 | ],
202 | ];
203 |
204 | $options['graphql_query_name'] = [
205 | 'category' => 'graphql',
206 | 'title' => $this->t('Query name'),
207 | 'value' => views_ui_truncate($this->getGraphQLQueryName(), 24),
208 | ];
209 | }
210 |
211 | /**
212 | * {@inheritdoc}
213 | */
214 | public function buildOptionsForm(&$form, FormStateInterface $form_state) {
215 | parent::buildOptionsForm($form, $form_state);
216 |
217 | switch ($form_state->get('section')) {
218 | case 'graphql_query_name':
219 | $form['#title'] .= $this->t('Query name');
220 | $form['graphql_query_name'] = [
221 | '#type' => 'textfield',
222 | '#description' => $this->t('This will be the graphQL query name.'),
223 | '#default_value' => $this->getGraphQLQueryName(),
224 | ];
225 | break;
226 | }
227 | }
228 |
229 | /**
230 | * {@inheritdoc}
231 | */
232 | public function submitOptionsForm(&$form, FormStateInterface $form_state) {
233 | parent::submitOptionsForm($form, $form_state);
234 | $section = $form_state->get('section');
235 | switch ($section) {
236 | case 'graphql_query_name':
237 | $this->setOption($section, $form_state->getValue($section));
238 | break;
239 | }
240 | }
241 |
242 | /**
243 | * {@inheritdoc}
244 | */
245 | public function execute() {
246 | return $this->view->execute();
247 | }
248 |
249 | /**
250 | * {@inheritdoc}
251 | */
252 | public function render() {
253 | $rows = (!empty($this->view->result) || $this->view->style_plugin->evenEmpty()) ? $this->view->style_plugin->render($this->view->result) : [];
254 |
255 | // Apply the cache metadata from the display plugin. This comes back as a
256 | // cache render array so we have to transform it back afterwards.
257 | $this->applyDisplayCacheabilityMetadata($this->view->element);
258 |
259 | return [
260 | 'view' => $this->view,
261 | 'rows' => $rows,
262 | 'cache' => CacheableMetadata::createFromRenderArray($this->view->element),
263 | ];
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/src/Plugin/views/exposed_form/GraphQL.php:
--------------------------------------------------------------------------------
1 | view->exposed_data = $this->view->getExposedInput();
26 |
27 | return NULL;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Plugin/views/row/GraphQLEntityRow.php:
--------------------------------------------------------------------------------
1 | entityTypeBundleInfo = $entityTypeBundleInfo;
84 | $this->languageManager = $languageManager;
85 | $this->entityTypeManager = $entityTypeManager;
86 | $this->entityRepository = $entityRepository;
87 | }
88 |
89 | /**
90 | * {@inheritdoc}
91 | */
92 | public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) {
93 | return new static(
94 | $configuration,
95 | $pluginId,
96 | $pluginDefinition,
97 | $container->get('entity_type.bundle.info'),
98 | $container->get('language_manager'),
99 | $container->get('entity_type.manager'),
100 | $container->get('entity.repository')
101 | );
102 | }
103 |
104 | /**
105 | * {@inheritdoc}
106 | */
107 | public function render($row) {
108 | if ($entity = $this->getEntityFromRow($row)) {
109 | return $this->view->getBaseEntityType() ? $this->getEntityTranslation($entity, $row) : $entity;
110 | }
111 |
112 | return NULL;
113 | }
114 |
115 | /**
116 | * {@inheritdoc}
117 | */
118 | protected function getEntityTranslationRenderer() {
119 | if ($this->view->getBaseEntityType()) {
120 | return $this->getEntityTranslationRendererBase();
121 | }
122 |
123 | return NULL;
124 | }
125 |
126 | /**
127 | * {@inheritdoc}
128 | */
129 | public function getEntityTypeManager() {
130 | return $this->entityTypeManager;
131 | }
132 |
133 | /**
134 | * {@inheritdoc}
135 | */
136 | public function getEntityRepository() {
137 | return $this->entityRepository;
138 | }
139 |
140 | /**
141 | * {@inheritdoc}
142 | */
143 | public function getEntityTypeId() {
144 | if ($entityType = $this->view->getBaseEntityType()) {
145 | return $entityType->id();
146 | }
147 |
148 | return NULL;
149 | }
150 |
151 | /**
152 | * {@inheritdoc}
153 | */
154 | protected function getEntityTypeBundleInfo() {
155 | return $this->entityTypeBundleInfo;
156 | }
157 |
158 | /**
159 | * {@inheritdoc}
160 | */
161 | protected function getLanguageManager() {
162 | return $this->languageManager;
163 | }
164 |
165 | /**
166 | * Retrieves the entity object from a result row.
167 | *
168 | * @param \Drupal\Views\ResultRow $row
169 | * The views result row object.
170 | *
171 | * @return null|\Drupal\Core\Entity\EntityInterface
172 | * The extracted entity object or NULL if it could not be retrieved.
173 | */
174 | protected function getEntityFromRow(ResultRow $row) {
175 | if (isset($row->_entity) && $row->_entity instanceof EntityInterface) {
176 | return $row->_entity;
177 | }
178 |
179 | if (isset($row->_object) && $row->_object instanceof EntityAdapter) {
180 | return $row->_object->getValue();
181 | }
182 |
183 | return NULL;
184 | }
185 |
186 | /**
187 | * {@inheritdoc}
188 | */
189 | protected function getView() {
190 | return $this->view;
191 | }
192 |
193 | /**
194 | * {@inheritdoc}
195 | */
196 | public function query() {
197 | parent::query();
198 |
199 | if ($this->view->getBaseEntityType()) {
200 | $this->getEntityTranslationRenderer()->query($this->view->getQuery());
201 | }
202 | }
203 |
204 | }
205 |
--------------------------------------------------------------------------------
/src/Plugin/views/row/GraphQLFieldRow.php:
--------------------------------------------------------------------------------
1 | options['field_options'])) {
55 | $options = (array) $this->options['field_options'];
56 | // Prepare a trimmed version of replacement aliases.
57 | $aliases = static::extractFromOptionsArray('alias', $options);
58 | $this->replacementAliases = array_filter(array_map('trim', $aliases));
59 | // Prepare an array of raw output field options.
60 | $this->rawOutputOptions = static::extractFromOptionsArray('raw_output', $options);
61 | $this->typeOptions = static::extractFromOptionsArray('type', $options);
62 | }
63 | }
64 |
65 | /**
66 | * {@inheritdoc}
67 | */
68 | protected function defineOptions() {
69 | $options = parent::defineOptions();
70 | $options['field_options'] = ['default' => []];
71 |
72 | return $options;
73 | }
74 |
75 | /**
76 | * {@inheritdoc}
77 | */
78 | public function buildOptionsForm(&$form, FormStateInterface $form_state) {
79 | parent::buildOptionsForm($form, $form_state);
80 |
81 | $form['field_options'] = [
82 | '#type' => 'table',
83 | '#header' => [
84 | $this->t('Field'),
85 | $this->t('Alias'),
86 | $this->t('Raw output'),
87 | $this->t('Type'),
88 | ],
89 | '#empty' => $this->t('You have no fields. Add some to your view.'),
90 | '#tree' => TRUE,
91 | ];
92 |
93 | $options = $this->options['field_options'];
94 |
95 | if ($fields = $this->view->display_handler->getOption('fields')) {
96 | foreach ($fields as $id => $field) {
97 | // Don't show the field if it has been excluded.
98 | if (!empty($field['exclude'])) {
99 | continue;
100 | }
101 |
102 | $form['field_options'][$id]['field'] = [
103 | '#markup' => $id,
104 | ];
105 |
106 | $form['field_options'][$id]['alias'] = [
107 | '#title' => $this->t('Alias for @id', ['@id' => $id]),
108 | '#title_display' => 'invisible',
109 | '#type' => 'textfield',
110 | '#default_value' => isset($options[$id]['alias']) ? $options[$id]['alias'] : '',
111 | '#element_validate' => [[$this, 'validateAliasName']],
112 | ];
113 |
114 | $form['field_options'][$id]['raw_output'] = [
115 | '#title' => $this->t('Raw output for @id', ['@id' => $id]),
116 | '#title_display' => 'invisible',
117 | '#type' => 'checkbox',
118 | '#default_value' => isset($options[$id]['raw_output']) ? $options[$id]['raw_output'] : '',
119 | ];
120 |
121 | $form['field_options'][$id]['type'] = [
122 | '#type' => 'select',
123 | '#options' => [
124 | 'String' => $this->t('String'),
125 | 'Int' => $this->t('Int'),
126 | 'Float' => $this->t('Float'),
127 | 'Boolean' => $this->t('Boolean'),
128 | ],
129 | '#default_value' => isset($options[$id]['type']) ? $options[$id]['type'] : 'String',
130 | ];
131 | }
132 | }
133 | }
134 |
135 | /**
136 | * Form element validation handler.
137 | */
138 | public function validateAliasName($element, FormStateInterface $form_state) {
139 | if (preg_match('@[^A-Za-z0-9_-]+@', $element['#value'])) {
140 | $form_state->setError($element, $this->t('The machine-readable name must contain only letters, numbers, dashes and underscores.'));
141 | }
142 | }
143 |
144 | /**
145 | * {@inheritdoc}
146 | */
147 | public function validateOptionsForm(&$form, FormStateInterface $form_state) {
148 | // Collect an array of aliases to validate.
149 | $aliases = static::extractFromOptionsArray('alias', $form_state->getValue(['row_options', 'field_options']));
150 |
151 | // If array filter returns empty, no values have been entered. Unique keys
152 | // should only be validated if we have some.
153 | if (($filtered = array_filter($aliases)) && (array_unique($filtered) !== $filtered)) {
154 | $form_state->setErrorByName('aliases', $this->t('All field aliases must be unique'));
155 | }
156 | }
157 |
158 | /**
159 | * {@inheritdoc}
160 | */
161 | public function render($row) {
162 | $output = [];
163 |
164 | foreach ($this->view->field as $id => $field) {
165 | // If the raw output option has been set, just get the raw value.
166 | if (!empty($this->rawOutputOptions[$id])) {
167 | $value = $field->getValue($row);
168 | }
169 | // Otherwise, pass this through the field advancedRender() method.
170 | else {
171 | $value = $field->advancedRender($row);
172 | }
173 |
174 | // Omit excluded fields from the rendered output.
175 | if (empty($field->options['exclude'])) {
176 | $output[$this->getFieldKeyAlias($id)] = $value;
177 | }
178 | }
179 |
180 | return $output;
181 | }
182 |
183 | /**
184 | * Return an alias for a field ID, as set in the options form.
185 | *
186 | * @param string $id
187 | * The field id to lookup an alias for.
188 | *
189 | * @return string
190 | * The matches user entered alias, or the original ID if nothing is found.
191 | */
192 | public function getFieldKeyAlias($id) {
193 | if (isset($this->replacementAliases[$id])) {
194 | return $this->replacementAliases[$id];
195 | }
196 |
197 | return $id;
198 | }
199 |
200 | /**
201 | * Return a GraphQL field type, as set in the options form.
202 | *
203 | * @param string $id
204 | * The field id to lookup a type for.
205 | *
206 | * @return string
207 | * The matches user entered type, or String.
208 | */
209 | public function getFieldType($id) {
210 | if (isset($this->typeOptions[$id])) {
211 | return $this->typeOptions[$id];
212 | }
213 |
214 | return 'String';
215 | }
216 |
217 | /**
218 | * Extracts a set of option values from a nested options array.
219 | *
220 | * @param string $key
221 | * The key to extract from each array item.
222 | * @param array $options
223 | * The options array to return values from.
224 | *
225 | * @return array
226 | * A regular one dimensional array of values.
227 | */
228 | protected static function extractFromOptionsArray($key, array $options) {
229 | return array_map(function($item) use ($key) {
230 | return isset($item[$key]) ? $item[$key] : NULL;
231 | }, $options);
232 | }
233 |
234 | }
235 |
--------------------------------------------------------------------------------
/src/Plugin/views/style/GraphQL.php:
--------------------------------------------------------------------------------
1 | view->result as $row_index => $row) {
45 | $this->view->row_index = $row_index;
46 | $rows[] = $this->view->rowPlugin->render($row);
47 | }
48 | unset($this->view->row_index);
49 |
50 | return $rows;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/ViewDeriverHelperTrait.php:
--------------------------------------------------------------------------------
1 | [
33 | 'type' => StringHelper::camelCase($id, 'contextual', 'filter', 'input'),
34 | ],
35 | ];
36 | }
37 |
38 | return [];
39 | }
40 |
41 | /**
42 | * Helper function to retrieve the sort arguments if any are exposed.
43 | *
44 | * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display
45 | * The display plugin.
46 | * @param string $id
47 | * The plugin derivative id.
48 | *
49 | * @return array
50 | * The sort arguments if any exposed sorts are available.
51 | */
52 | protected function getSortArguments(DisplayPluginInterface $display, $id) {
53 | $sorts = array_filter($display->getOption('sorts') ?: [], function ($sort) {
54 | return $sort['exposed'];
55 | });
56 | return $sorts ? [
57 | 'sortDirection' => [
58 | 'type' => 'ViewSortDirection',
59 | 'default' => 'asc',
60 | ],
61 | 'sortBy' => [
62 | 'type' => StringHelper::camelCase($id, 'sort', 'by'),
63 | ],
64 | ] : [];
65 | }
66 |
67 | /**
68 | * Helper function to return the filter argument if applicable.
69 | *
70 | * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display
71 | * The display plugin.
72 | * @param string $id
73 | * The plugin derivative id.
74 | *
75 | * @return array
76 | * The filter argument if any exposed filters are available.
77 | */
78 | protected function getFilterArguments(DisplayPluginInterface $display, $id) {
79 | $filters = array_filter($display->getOption('filters') ?: [], function ($filter) {
80 | return array_key_exists('exposed', $filter) && $filter['exposed'];
81 | });
82 |
83 | return !empty($filters) ? [
84 | 'filter' => [
85 | 'type' => $display->getGraphQLFilterInputName(),
86 | ],
87 | ] : [];
88 | }
89 |
90 | /**
91 | * Helper function to retrieve the pager arguments if the display is paged.
92 | *
93 | * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display
94 | * The display plugin.
95 | *
96 | * @return array
97 | * An array of pager arguments if the view display is paged.
98 | */
99 | protected function getPagerArguments(DisplayPluginInterface $display) {
100 | return $this->isPaged($display) ? [
101 | 'page' => ['type' => 'Int', 'default' => $this->getPagerOffset($display)],
102 | 'pageSize' => [
103 | 'type' => 'Int',
104 | 'default' => $this->getPagerLimit($display),
105 | ],
106 | ] : [];
107 | }
108 |
109 | /**
110 | * Helper function to retrieve the types that the view can be attached to.
111 | *
112 | * @param array $arguments
113 | * An array containing information about the available arguments.
114 | * @param array $types
115 | * Types where it needs to be added.
116 | *
117 | * @return array
118 | * An array of additional types the view can be embedded in.
119 | */
120 | protected function getTypes(array $arguments, array $types = ['Root']) {
121 |
122 | if (empty($arguments)) {
123 | return $types;
124 | }
125 |
126 | foreach ($arguments as $argument) {
127 | // Depending on whether bundles are known, we expose the view field
128 | // either on the interface (e.g. Node) or on the type (e.g. NodePage)
129 | // level. Here we specify types managed by other graphql_* modules,
130 | // yet we don't define these modules as dependencies. If types are not
131 | // in the schema, the resulting GraphQL field will be attached to
132 | // nowhere, so it won't go into the schema.
133 | if (empty($argument['bundles']) && empty($argument['entity_type'])) {
134 | continue;
135 | }
136 |
137 | if (empty($argument['bundles'])) {
138 | $types = array_merge($types, [StringHelper::camelCase($argument['entity_type'])]);
139 | }
140 | else {
141 | $types = array_merge($types, array_map(function ($bundle) use ($argument) {
142 | return StringHelper::camelCase($argument['entity_type'], $bundle);
143 | }, array_keys($argument['bundles'])));
144 | }
145 | }
146 |
147 | return $types;
148 | }
149 |
150 | /**
151 | * Check if a pager is configured.
152 | *
153 | * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display
154 | * The display configuration.
155 | *
156 | * @return bool
157 | * Flag indicating if the view is configured with a pager.
158 | */
159 | protected function isPaged(DisplayPluginInterface $display) {
160 | $pagerOptions = $display->getOption('pager');
161 | return isset($pagerOptions['type']) && in_array($pagerOptions['type'], [
162 | 'full',
163 | 'mini',
164 | ]);
165 | }
166 |
167 | /**
168 | * Returns a view display object.
169 | *
170 | * @param \Drupal\views\ViewEntityInterface $view
171 | * The view object.
172 | * @param string $displayId
173 | * The display ID to use.
174 | *
175 | * @return \Drupal\views\Plugin\views\display\DisplayPluginInterface
176 | * The view display object.
177 | */
178 | protected function getViewDisplay(ViewEntityInterface $view, $displayId) {
179 | $viewExecutable = $view->getExecutable();
180 | $viewExecutable->setDisplay($displayId);
181 | return $viewExecutable->getDisplay();
182 | }
183 |
184 | /**
185 | * Get the configured default limit.
186 | *
187 | * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display
188 | * The display configuration.
189 | *
190 | * @return int
191 | * The default limit.
192 | */
193 | protected function getPagerLimit(DisplayPluginInterface $display) {
194 | $pagerOptions = $display->getOption('pager');
195 | return NestedArray::getValue($pagerOptions, [
196 | 'options',
197 | 'items_per_page',
198 | ]) ?: 0;
199 | }
200 |
201 | /**
202 | * Get the configured default offset.
203 | *
204 | * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display
205 | * The display configuration.
206 | *
207 | * @return int
208 | * The default offset.
209 | */
210 | protected function getPagerOffset(DisplayPluginInterface $display) {
211 | $pagerOptions = $display->getOption('pager');
212 | return NestedArray::getValue($pagerOptions, [
213 | 'options',
214 | 'offset',
215 | ]) ?: 0;
216 | }
217 |
218 | /**
219 | * Check if a certain interface exists.
220 | *
221 | * @param string $interface
222 | * The GraphQL interface name.
223 | * @param \Drupal\Component\Plugin\PluginManagerInterface $interfacePluginManager
224 | * Plugin interface manager.
225 | *
226 | * @return bool
227 | * Boolean flag indicating if the interface exists.
228 | */
229 | protected function interfaceExists($interface, PluginManagerInterface $interfacePluginManager) {
230 | return (bool) array_filter($interfacePluginManager->getDefinitions(), function ($definition) use ($interface) {
231 | return $definition['name'] === $interface;
232 | });
233 | }
234 |
235 | /**
236 | * Retrieves the type the view's rows resolve to.
237 | *
238 | * @param \Drupal\views\ViewEntityInterface $view
239 | * The view entity.
240 | * @param string $displayId
241 | * The id of the current display.
242 | * @param \Drupal\Component\Plugin\PluginManagerInterface $interfacePluginManager
243 | * Interface plugin manager.
244 | *
245 | * @return null|string
246 | * The name of the type or NULL if the type could not be derived.
247 | */
248 | protected function getRowResolveType(ViewEntityInterface $view, $displayId, PluginManagerInterface $interfacePluginManager) {
249 | /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */
250 | $display = $this->getViewDisplay($view, $displayId);
251 | $rowPlugin = $display->getPlugin('row');
252 |
253 | if ($rowPlugin instanceof GraphQLFieldRow) {
254 | return StringHelper::camelCase($display->getGraphQLRowName());
255 | }
256 |
257 | if ($rowPlugin instanceof GraphQLEntityRow) {
258 | $executable = $view->getExecutable();
259 | $executable->setDisplay($displayId);
260 |
261 | if ($entityType = $executable->getBaseEntityType()) {
262 | $typeName = $entityType->id();
263 | $typeNameCamel = StringHelper::camelCase($typeName);
264 | if ($this->interfaceExists($typeNameCamel, $interfacePluginManager)) {
265 | $filters = $executable->getDisplay()->getOption('filters');
266 | $dataTable = $entityType->getDataTable();
267 | $bundleKey = $entityType->getKey('bundle');
268 |
269 | foreach ($filters as $filter) {
270 | $isBundleFilter = $filter['table'] == $dataTable && $filter['field'] == $bundleKey;
271 | $isSingleValued = is_array($filter['value']) && count($filter['value']) == 1;
272 | $isExposed = isset($filter['exposed']) && $filter['exposed'];
273 | if ($isBundleFilter && $isSingleValued && !$isExposed) {
274 | $bundle = reset($filter['value']);
275 | $typeName .= "_$bundle";
276 | break;
277 | }
278 | }
279 |
280 | return StringHelper::camelCase($typeName);
281 | }
282 | }
283 |
284 | return 'Entity';
285 | }
286 |
287 | return NULL;
288 | }
289 |
290 | /**
291 | * Returns a view style object.
292 | *
293 | * @param \Drupal\views\ViewEntityInterface $view
294 | * The view object.
295 | * @param string $displayId
296 | * The display ID to use.
297 | *
298 | * @return \Drupal\views\Plugin\views\style\StylePluginBase
299 | * The view style object.
300 | */
301 | protected function getViewStyle(ViewEntityInterface $view, $displayId) {
302 | $viewExecutable = $view->getExecutable();
303 | $viewExecutable->setDisplay($displayId);
304 | return $viewExecutable->getStyle();
305 | }
306 |
307 | /**
308 | * Returns cache metadata plugin definitions.
309 | *
310 | * @param \Drupal\views\ViewEntityInterface $view
311 | * The view object.
312 | * @param \Drupal\views\Plugin\views\display\DisplayPluginInterface $display
313 | * The view display.
314 | *
315 | * @return array
316 | * The cache metadata definitions for the plugin definition.
317 | */
318 | protected function getCacheMetadataDefinition(ViewEntityInterface $view, DisplayPluginInterface $display) {
319 | $metadata = $display->getCacheMetadata()
320 | ->addCacheTags($view->getCacheTags())
321 | ->addCacheContexts($view->getCacheContexts())
322 | ->mergeCacheMaxAge($view->getCacheMaxAge());
323 |
324 | return [
325 | 'schema_cache_tags' => $metadata->getCacheTags(),
326 | 'schema_cache_max_age' => $metadata->getCacheMaxAge(),
327 | 'response_cache_contexts' => array_filter($metadata->getCacheContexts(), function ($context) {
328 | // Don't emit the url cache contexts.
329 | return $context !== 'url' && strpos($context, 'url.') !== 0;
330 | }),
331 | ];
332 | }
333 |
334 | /**
335 | * Returns information about view arguments (contextual filters).
336 | *
337 | * @param array $viewArguments
338 | * The "arguments" option of a view display.
339 | *
340 | * @return array
341 | * Arguments information keyed by the argument ID. Subsequent array keys:
342 | * - index: argument index.
343 | * - entity_type: target entity type.
344 | * - bundles: target bundles (can be empty).
345 | *
346 | * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
347 | */
348 | protected function getArgumentsInfo(array $viewArguments) {
349 | $argumentsInfo = [];
350 | /* @var \Drupal\Core\Entity\EntityTypeManager $entityTypeManager */
351 | $entityTypeManager = \Drupal::service('entity_type.manager');
352 |
353 | $index = 0;
354 | foreach ($viewArguments as $argumentId => $argument) {
355 | $info = [
356 | 'index' => $index,
357 | 'entity_type' => NULL,
358 | 'bundles' => [],
359 | ];
360 |
361 | if (isset($argument['entity_type']) && isset($argument['entity_field'])) {
362 | $entityType = $entityTypeManager->getDefinition($argument['entity_type']);
363 | if ($entityType) {
364 | $idField = $entityType->getKey('id');
365 | if ($idField === $argument['entity_field']) {
366 | $info['entity_type'] = $argument['entity_type'];
367 | if (
368 | $argument['specify_validation'] &&
369 | strpos($argument['validate']['type'], 'entity:') === 0 &&
370 | !empty($argument['validate_options']['bundles'])
371 | ) {
372 | $info['bundles'] = $argument['validate_options']['bundles'];
373 | }
374 | }
375 | }
376 | }
377 |
378 | $argumentsInfo[$argumentId] = $info;
379 | $index++;
380 | }
381 |
382 | return $argumentsInfo;
383 | }
384 |
385 | }
386 |
--------------------------------------------------------------------------------
/tests/modules/graphql_views_test/config/install/views.view.graphql_bundle_test.yml:
--------------------------------------------------------------------------------
1 | uuid: 8abe765b-8d05-4b97-8a39-0be2ac93f439
2 | langcode: en
3 | status: true
4 | dependencies:
5 | config:
6 | - node.type.test
7 | module:
8 | - graphql
9 | - node
10 | - user
11 | id: graphql_bundle_test
12 | label: 'GraphQL Bundle Test'
13 | module: views
14 | description: ''
15 | tag: ''
16 | base_table: node_field_data
17 | base_field: nid
18 | core: 8.x
19 | core_version_requirement: ^8 || ^9
20 | display:
21 | default:
22 | display_plugin: default
23 | id: default
24 | display_title: Master
25 | position: 0
26 | display_options:
27 | access:
28 | type: perm
29 | options:
30 | perm: 'access content'
31 | cache:
32 | type: tag
33 | options: { }
34 | query:
35 | type: views_query
36 | options:
37 | disable_sql_rewrite: false
38 | distinct: false
39 | replica: false
40 | query_comment: ''
41 | query_tags: { }
42 | exposed_form:
43 | type: basic
44 | options:
45 | submit_button: Apply
46 | reset_button: false
47 | reset_button_label: Reset
48 | exposed_sorts_label: 'Sort by'
49 | expose_sort_order: true
50 | sort_asc_label: Asc
51 | sort_desc_label: Desc
52 | pager:
53 | type: mini
54 | options:
55 | items_per_page: 10
56 | offset: 0
57 | id: 0
58 | total_pages: null
59 | expose:
60 | items_per_page: false
61 | items_per_page_label: 'Items per page'
62 | items_per_page_options: '5, 10, 25, 50'
63 | items_per_page_options_all: false
64 | items_per_page_options_all_label: '- All -'
65 | offset: false
66 | offset_label: Offset
67 | tags:
68 | previous: ‹‹
69 | next: ››
70 | style:
71 | type: default
72 | options:
73 | grouping: { }
74 | row_class: ''
75 | default_row_class: true
76 | uses_fields: false
77 | row:
78 | type: fields
79 | options:
80 | inline: { }
81 | separator: ''
82 | hide_empty: false
83 | default_field_elements: true
84 | fields:
85 | title:
86 | id: title
87 | table: node_field_data
88 | field: title
89 | entity_type: node
90 | entity_field: title
91 | label: ''
92 | alter:
93 | alter_text: false
94 | make_link: false
95 | absolute: false
96 | trim: false
97 | word_boundary: false
98 | ellipsis: false
99 | strip_tags: false
100 | html: false
101 | hide_empty: false
102 | empty_zero: false
103 | settings:
104 | link_to_entity: true
105 | plugin_id: field
106 | relationship: none
107 | group_type: group
108 | admin_label: ''
109 | exclude: false
110 | element_type: ''
111 | element_class: ''
112 | element_label_type: ''
113 | element_label_class: ''
114 | element_label_colon: true
115 | element_wrapper_type: ''
116 | element_wrapper_class: ''
117 | element_default_classes: true
118 | empty: ''
119 | hide_alter_empty: true
120 | click_sort_column: value
121 | type: string
122 | group_column: value
123 | group_columns: { }
124 | group_rows: true
125 | delta_limit: 0
126 | delta_offset: 0
127 | delta_reversed: false
128 | delta_first_last: false
129 | multi_type: separator
130 | separator: ', '
131 | field_api_classes: false
132 | filters:
133 | status:
134 | value: '1'
135 | table: node_field_data
136 | field: status
137 | plugin_id: boolean
138 | entity_type: node
139 | entity_field: status
140 | id: status
141 | expose:
142 | operator: ''
143 | group: 1
144 | type:
145 | id: type
146 | table: node_field_data
147 | field: type
148 | value:
149 | test: test
150 | entity_type: node
151 | entity_field: type
152 | plugin_id: bundle
153 | sorts:
154 | nid:
155 | id: nid
156 | table: node_field_data
157 | field: nid
158 | relationship: none
159 | group_type: group
160 | admin_label: ''
161 | order: ASC
162 | exposed: false
163 | expose:
164 | label: ''
165 | entity_type: node
166 | entity_field: nid
167 | plugin_id: standard
168 |
169 | header: { }
170 | footer: { }
171 | empty: { }
172 | relationships: { }
173 | arguments: { }
174 | display_extenders: { }
175 | cache_metadata:
176 | max-age: -1
177 | contexts:
178 | - 'languages:language_content'
179 | - 'languages:language_interface'
180 | - url.query_args
181 | - 'user.node_grants:view'
182 | - user.permissions
183 | tags: { }
184 | graphql_1:
185 | display_plugin: graphql
186 | id: graphql_1
187 | display_title: GraphQL
188 | position: 1
189 | display_options:
190 | display_extenders: { }
191 | pager:
192 | type: some
193 | options:
194 | items_per_page: 1
195 | offset: 0
196 | cache_metadata:
197 | max-age: -1
198 | contexts:
199 | - 'languages:language_content'
200 | - 'languages:language_interface'
201 | - 'user.node_grants:view'
202 | - user.permissions
203 | tags: { }
204 |
--------------------------------------------------------------------------------
/tests/modules/graphql_views_test/config/install/views.view.graphql_test.yml:
--------------------------------------------------------------------------------
1 | uuid: 287a1f02-3fd1-4b5b-8c77-76855fa7f308
2 | langcode: en
3 | status: true
4 | dependencies:
5 | config:
6 | - taxonomy.vocabulary.tags
7 | module:
8 | - graphql
9 | - node
10 | - taxonomy
11 | - user
12 | id: graphql_test
13 | label: 'GraphQL Test'
14 | module: views
15 | description: 'Views configurations for GraphQL integration testing.'
16 | tag: ''
17 | base_table: node_field_data
18 | base_field: nid
19 | core: 8.x
20 | core_version_requirement: ^8 || ^9
21 | display:
22 | default:
23 | display_plugin: default
24 | id: default
25 | display_title: Master
26 | position: 0
27 | display_options:
28 | access:
29 | type: perm
30 | options:
31 | perm: 'access content'
32 | cache:
33 | type: tag
34 | options: { }
35 | query:
36 | type: views_query
37 | options:
38 | disable_sql_rewrite: false
39 | distinct: false
40 | replica: false
41 | query_comment: ''
42 | query_tags: { }
43 | exposed_form:
44 | type: basic
45 | options:
46 | submit_button: Apply
47 | reset_button: false
48 | reset_button_label: Reset
49 | exposed_sorts_label: 'Sort by'
50 | expose_sort_order: true
51 | sort_asc_label: Asc
52 | sort_desc_label: Desc
53 | pager:
54 | type: mini
55 | options:
56 | items_per_page: 10
57 | offset: 0
58 | id: 0
59 | total_pages: null
60 | expose:
61 | items_per_page: false
62 | items_per_page_label: 'Items per page'
63 | items_per_page_options: '5, 10, 25, 50'
64 | items_per_page_options_all: false
65 | items_per_page_options_all_label: '- All -'
66 | offset: false
67 | offset_label: Offset
68 | tags:
69 | previous: ‹‹
70 | next: ››
71 | style:
72 | type: default
73 | options:
74 | grouping: { }
75 | row_class: ''
76 | default_row_class: true
77 | uses_fields: false
78 | row:
79 | type: graphql_entity
80 | options: { }
81 | fields:
82 | title:
83 | id: title
84 | table: node_field_data
85 | field: title
86 | entity_type: node
87 | entity_field: title
88 | label: ''
89 | alter:
90 | alter_text: false
91 | make_link: false
92 | absolute: false
93 | trim: false
94 | word_boundary: false
95 | ellipsis: false
96 | strip_tags: false
97 | html: false
98 | hide_empty: false
99 | empty_zero: false
100 | settings:
101 | link_to_entity: true
102 | plugin_id: field
103 | relationship: none
104 | group_type: group
105 | admin_label: ''
106 | exclude: false
107 | element_type: ''
108 | element_class: ''
109 | element_label_type: ''
110 | element_label_class: ''
111 | element_label_colon: true
112 | element_wrapper_type: ''
113 | element_wrapper_class: ''
114 | element_default_classes: true
115 | empty: ''
116 | hide_alter_empty: true
117 | click_sort_column: value
118 | type: string
119 | group_column: value
120 | group_columns: { }
121 | group_rows: true
122 | delta_limit: 0
123 | delta_offset: 0
124 | delta_reversed: false
125 | delta_first_last: false
126 | multi_type: separator
127 | separator: ', '
128 | field_api_classes: false
129 | filters:
130 | title:
131 | id: title
132 | table: node_field_data
133 | field: title
134 | relationship: none
135 | group_type: group
136 | admin_label: ''
137 | operator: contains
138 | value: ''
139 | group: 1
140 | exposed: true
141 | expose:
142 | operator_id: title_op
143 | label: Title
144 | description: ''
145 | use_operator: false
146 | operator: title_op
147 | identifier: title
148 | required: false
149 | remember: false
150 | multiple: false
151 | remember_roles:
152 | authenticated: authenticated
153 | anonymous: '0'
154 | administrator: '0'
155 | is_grouped: false
156 | group_info:
157 | label: ''
158 | description: ''
159 | identifier: ''
160 | optional: true
161 | widget: select
162 | multiple: false
163 | remember: false
164 | default_group: All
165 | default_group_multiple: { }
166 | group_items: { }
167 | entity_type: node
168 | entity_field: title
169 | plugin_id: string
170 | field_tags_target_id:
171 | id: field_tags_target_id
172 | table: node__field_tags
173 | field: field_tags_target_id
174 | relationship: none
175 | group_type: group
176 | admin_label: Tags
177 | operator: or
178 | value: { }
179 | group: 1
180 | exposed: true
181 | expose:
182 | operator_id: field_tags_target_id_op
183 | label: Tags
184 | description: ''
185 | use_operator: false
186 | operator: field_tags_target_id_op
187 | identifier: field_tags
188 | required: false
189 | remember: false
190 | multiple: true
191 | remember_roles:
192 | authenticated: authenticated
193 | anonymous: '0'
194 | administrator: '0'
195 | reduce: false
196 | is_grouped: false
197 | group_info:
198 | label: ''
199 | description: ''
200 | identifier: ''
201 | optional: true
202 | widget: select
203 | multiple: false
204 | remember: false
205 | default_group: All
206 | default_group_multiple: { }
207 | group_items: { }
208 | reduce_duplicates: false
209 | type: select
210 | limit: true
211 | vid: tags
212 | hierarchy: false
213 | error_message: true
214 | plugin_id: taxonomy_index_tid
215 | type:
216 | id: type
217 | table: node_field_data
218 | field: type
219 | relationship: none
220 | group_type: group
221 | admin_label: ''
222 | operator: in
223 | value:
224 | test: test
225 | group: 1
226 | exposed: true
227 | expose:
228 | operator_id: type_op
229 | label: 'Content type'
230 | description: ''
231 | use_operator: false
232 | operator: type_op
233 | identifier: node_type
234 | required: false
235 | remember: false
236 | multiple: false
237 | remember_roles:
238 | authenticated: authenticated
239 | anonymous: '0'
240 | administrator: '0'
241 | reduce: true
242 | is_grouped: false
243 | group_info:
244 | label: ''
245 | description: ''
246 | identifier: ''
247 | optional: true
248 | widget: select
249 | multiple: false
250 | remember: false
251 | default_group: All
252 | default_group_multiple: { }
253 | group_items: { }
254 | entity_type: node
255 | entity_field: type
256 | plugin_id: bundle
257 | filter_groups:
258 | operator: AND
259 | groups:
260 | 1: AND
261 | sorts:
262 | nid:
263 | id: nid
264 | table: node_field_data
265 | field: nid
266 | relationship: none
267 | group_type: group
268 | admin_label: ''
269 | order: ASC
270 | exposed: false
271 | expose:
272 | label: ''
273 | entity_type: node
274 | entity_field: nid
275 | plugin_id: standard
276 | header: { }
277 | footer: { }
278 | empty: { }
279 | relationships: { }
280 | arguments: { }
281 | display_extenders: { }
282 | cache_metadata:
283 | max-age: -1
284 | contexts:
285 | - 'languages:language_content'
286 | - 'languages:language_interface'
287 | - url
288 | - url.query_args
289 | - user
290 | - 'user.node_grants:view'
291 | - user.permissions
292 | tags: { }
293 | contextual_node:
294 | display_plugin: graphql
295 | id: contextual_node
296 | display_title: 'Contextual: Node'
297 | position: 6
298 | display_options:
299 | display_extenders: { }
300 | display_description: ''
301 | arguments:
302 | nid:
303 | id: nid
304 | table: node_field_data
305 | field: nid
306 | relationship: none
307 | group_type: group
308 | admin_label: ''
309 | default_action: ignore
310 | exception:
311 | value: all
312 | title_enable: false
313 | title: All
314 | title_enable: false
315 | title: ''
316 | default_argument_type: fixed
317 | default_argument_options:
318 | argument: ''
319 | default_argument_skip_url: false
320 | summary_options:
321 | base_path: ''
322 | count: true
323 | items_per_page: 25
324 | override: false
325 | summary:
326 | sort_order: asc
327 | number_of_records: 0
328 | format: default_summary
329 | specify_validation: false
330 | validate:
331 | type: none
332 | fail: 'not found'
333 | validate_options: { }
334 | break_phrase: false
335 | not: false
336 | entity_type: node
337 | entity_field: nid
338 | plugin_id: node_nid
339 | defaults:
340 | arguments: false
341 | cache_metadata:
342 | max-age: -1
343 | contexts:
344 | - 'languages:language_content'
345 | - 'languages:language_interface'
346 | - url
347 | - user
348 | - 'user.node_grants:view'
349 | - user.permissions
350 | tags: { }
351 | contextual_node_and_nodetest:
352 | display_plugin: graphql
353 | id: contextual_node_and_nodetest
354 | display_title: 'Contextual: Node and NodeTest'
355 | position: 8
356 | display_options:
357 | display_extenders: { }
358 | display_description: ''
359 | arguments:
360 | nid:
361 | id: nid
362 | table: node_field_data
363 | field: nid
364 | relationship: none
365 | group_type: group
366 | admin_label: ''
367 | default_action: ignore
368 | exception:
369 | value: all
370 | title_enable: false
371 | title: All
372 | title_enable: false
373 | title: ''
374 | default_argument_type: fixed
375 | default_argument_options:
376 | argument: ''
377 | default_argument_skip_url: false
378 | summary_options:
379 | base_path: ''
380 | count: true
381 | items_per_page: 25
382 | override: false
383 | summary:
384 | sort_order: asc
385 | number_of_records: 0
386 | format: default_summary
387 | specify_validation: false
388 | validate:
389 | type: none
390 | fail: 'not found'
391 | validate_options: { }
392 | break_phrase: false
393 | not: false
394 | entity_type: node
395 | entity_field: nid
396 | plugin_id: node_nid
397 | nid_1:
398 | id: nid_1
399 | table: node_field_data
400 | field: nid
401 | relationship: none
402 | group_type: group
403 | admin_label: ''
404 | default_action: ignore
405 | exception:
406 | value: all
407 | title_enable: false
408 | title: All
409 | title_enable: false
410 | title: ''
411 | default_argument_type: fixed
412 | default_argument_options:
413 | argument: ''
414 | default_argument_skip_url: false
415 | summary_options:
416 | base_path: ''
417 | count: true
418 | items_per_page: 25
419 | override: false
420 | summary:
421 | sort_order: asc
422 | number_of_records: 0
423 | format: default_summary
424 | specify_validation: true
425 | validate:
426 | type: 'entity:node'
427 | fail: 'not found'
428 | validate_options:
429 | bundles:
430 | test: test
431 | operation: view
432 | multiple: 0
433 | access: false
434 | break_phrase: false
435 | not: false
436 | entity_type: node
437 | entity_field: nid
438 | plugin_id: node_nid
439 | defaults:
440 | arguments: false
441 | cache_metadata:
442 | max-age: -1
443 | contexts:
444 | - 'languages:language_content'
445 | - 'languages:language_interface'
446 | - url
447 | - user
448 | - 'user.node_grants:view'
449 | - user.permissions
450 | tags: { }
451 | contextual_nodetest:
452 | display_plugin: graphql
453 | id: contextual_nodetest
454 | display_title: 'Contextual: NodeTest'
455 | position: 7
456 | display_options:
457 | display_extenders: { }
458 | display_description: ''
459 | arguments:
460 | nid:
461 | id: nid
462 | table: node_field_data
463 | field: nid
464 | relationship: none
465 | group_type: group
466 | admin_label: ''
467 | default_action: ignore
468 | exception:
469 | value: all
470 | title_enable: false
471 | title: All
472 | title_enable: false
473 | title: ''
474 | default_argument_type: fixed
475 | default_argument_options:
476 | argument: ''
477 | default_argument_skip_url: false
478 | summary_options:
479 | base_path: ''
480 | count: true
481 | items_per_page: 25
482 | override: false
483 | summary:
484 | sort_order: asc
485 | number_of_records: 0
486 | format: default_summary
487 | specify_validation: true
488 | validate:
489 | type: 'entity:node'
490 | fail: 'not found'
491 | validate_options:
492 | bundles:
493 | test: test
494 | operation: view
495 | multiple: 0
496 | access: false
497 | break_phrase: false
498 | not: false
499 | entity_type: node
500 | entity_field: nid
501 | plugin_id: node_nid
502 | defaults:
503 | arguments: false
504 | cache_metadata:
505 | max-age: -1
506 | contexts:
507 | - 'languages:language_content'
508 | - 'languages:language_interface'
509 | - url
510 | - user
511 | - 'user.node_grants:view'
512 | - user.permissions
513 | tags: { }
514 | contextual_title_arg:
515 | display_plugin: graphql
516 | id: contextual_title_arg
517 | display_title: 'Contextual: title arg'
518 | position: 5
519 | display_options:
520 | display_extenders: { }
521 | arguments:
522 | title:
523 | id: title
524 | table: node_field_data
525 | field: title
526 | relationship: none
527 | group_type: group
528 | admin_label: ''
529 | default_action: ignore
530 | exception:
531 | value: all
532 | title_enable: false
533 | title: All
534 | title_enable: false
535 | title: ''
536 | default_argument_type: fixed
537 | default_argument_options:
538 | argument: ''
539 | default_argument_skip_url: false
540 | summary_options:
541 | base_path: ''
542 | count: true
543 | items_per_page: 25
544 | override: false
545 | summary:
546 | sort_order: asc
547 | number_of_records: 0
548 | format: default_summary
549 | specify_validation: false
550 | validate:
551 | type: none
552 | fail: 'not found'
553 | validate_options: { }
554 | glossary: false
555 | limit: 0
556 | case: none
557 | path_case: none
558 | transform_dash: false
559 | break_phrase: false
560 | entity_type: node
561 | entity_field: title
562 | plugin_id: string
563 | defaults:
564 | arguments: false
565 | display_description: ''
566 | cache_metadata:
567 | max-age: -1
568 | contexts:
569 | - 'languages:language_content'
570 | - 'languages:language_interface'
571 | - url
572 | - user
573 | - 'user.node_grants:view'
574 | - user.permissions
575 | tags: { }
576 | filtered:
577 | display_plugin: graphql
578 | id: filtered
579 | display_title: Filtered
580 | position: 4
581 | display_options:
582 | display_extenders: { }
583 | display_description: ''
584 | filters: { }
585 | defaults:
586 | filters: true
587 | filter_groups: true
588 | pager:
589 | type: none
590 | options:
591 | offset: 0
592 | cache_metadata:
593 | max-age: -1
594 | contexts:
595 | - 'languages:language_content'
596 | - 'languages:language_interface'
597 | - url
598 | - user
599 | - 'user.node_grants:view'
600 | - user.permissions
601 | tags: { }
602 | paged:
603 | display_plugin: graphql
604 | id: paged
605 | display_title: Paged
606 | position: 2
607 | display_options:
608 | display_extenders: { }
609 | display_description: ''
610 | pager:
611 | type: full
612 | options:
613 | items_per_page: 2
614 | offset: 0
615 | id: 0
616 | total_pages: null
617 | tags:
618 | previous: '‹ Previous'
619 | next: 'Next ›'
620 | first: '« First'
621 | last: 'Last »'
622 | expose:
623 | items_per_page: false
624 | items_per_page_label: 'Items per page'
625 | items_per_page_options: '5, 10, 25, 50'
626 | items_per_page_options_all: false
627 | items_per_page_options_all_label: '- All -'
628 | offset: false
629 | offset_label: Offset
630 | quantity: 9
631 | cache_metadata:
632 | max-age: -1
633 | contexts:
634 | - 'languages:language_content'
635 | - 'languages:language_interface'
636 | - url
637 | - url.query_args
638 | - user
639 | - 'user.node_grants:view'
640 | - user.permissions
641 | tags: { }
642 | simple:
643 | display_plugin: graphql
644 | id: simple
645 | display_title: Simple
646 | position: 1
647 | display_options:
648 | display_extenders: { }
649 | display_description: ''
650 | pager:
651 | type: some
652 | options:
653 | items_per_page: 3
654 | offset: 0
655 | cache_metadata:
656 | max-age: -1
657 | contexts:
658 | - 'languages:language_content'
659 | - 'languages:language_interface'
660 | - url
661 | - user
662 | - 'user.node_grants:view'
663 | - user.permissions
664 | tags: { }
665 | sorted:
666 | display_plugin: graphql
667 | id: sorted
668 | display_title: Sorted
669 | position: 3
670 | display_options:
671 | display_extenders: { }
672 | display_description: ''
673 | sorts:
674 | nid:
675 | id: nid
676 | table: node_field_data
677 | field: nid
678 | relationship: none
679 | group_type: group
680 | admin_label: ''
681 | order: ASC
682 | exposed: true
683 | expose:
684 | label: ID
685 | entity_type: node
686 | entity_field: nid
687 | plugin_id: standard
688 | title:
689 | id: title
690 | table: node_field_data
691 | field: title
692 | relationship: none
693 | group_type: group
694 | admin_label: ''
695 | order: ASC
696 | exposed: true
697 | expose:
698 | label: Title
699 | entity_type: node
700 | entity_field: title
701 | plugin_id: standard
702 | defaults:
703 | sorts: false
704 | pager:
705 | type: some
706 | options:
707 | items_per_page: 3
708 | offset: 0
709 | cache_metadata:
710 | max-age: -1
711 | contexts:
712 | - 'languages:language_content'
713 | - 'languages:language_interface'
714 | - url
715 | - 'url.query_args:sort_by'
716 | - 'url.query_args:sort_order'
717 | - user
718 | - 'user.node_grants:view'
719 | - user.permissions
720 | tags: { }
721 |
--------------------------------------------------------------------------------
/tests/modules/graphql_views_test/graphql_views_test.features.yml:
--------------------------------------------------------------------------------
1 | required: true
2 |
--------------------------------------------------------------------------------
/tests/modules/graphql_views_test/graphql_views_test.info.yml:
--------------------------------------------------------------------------------
1 | name: 'GraphQL Views Test'
2 | description: 'Test configurations for GraphQL views integration.'
3 | type: module
4 | core: 8.x
5 | core_version_requirement: ^8 || ^9
6 | dependencies:
7 | - graphql_views
8 | - graphql_content_test
9 | - node
10 | - taxonomy
11 | - user
12 | - views
13 |
--------------------------------------------------------------------------------
/tests/modules/graphql_views_test/graphql_views_test.module:
--------------------------------------------------------------------------------
1 | storage->id() . ':' . $view->current_display;
8 | if (!isset($args[$id])) {
9 | $args[$id] = [];
10 | }
11 | $args[$id][] = $view->args;
12 | }
13 |
--------------------------------------------------------------------------------
/tests/queries/contextual.gql:
--------------------------------------------------------------------------------
1 | query ($test2NodeId: String!) {
2 |
3 | # graphql_test:contextual_title_arg
4 | title_arg0:graphqlTestContextualTitleArgView {
5 | results {
6 | entityId
7 | }
8 | }
9 |
10 | title_arg1:graphqlTestContextualTitleArgView (contextualFilter: {title: "X"}) {
11 | results {
12 | entityId
13 | }
14 | }
15 |
16 | # graphql_test:contextual_node
17 | node0:graphqlTestContextualNodeView {
18 | results {
19 | entityId
20 | }
21 | }
22 |
23 | node1:graphqlTestContextualNodeView (contextualFilter: {nid: "X"}) {
24 | results {
25 | entityId
26 | }
27 | }
28 |
29 | node2:nodeById (id: "1", language: EN) {
30 | ... on Node {
31 | graphqlTestContextualNodeView {
32 | results {
33 | entityId
34 | }
35 | }
36 | }
37 | }
38 | node3:nodeById (id: "1", language: EN) {
39 | ... on Node {
40 | graphqlTestContextualNodeView (contextualFilter: {nid: "X"}) {
41 | results {
42 | entityId
43 | }
44 | }
45 | }
46 | }
47 | node4:nodeById (id: "1", language: EN) {
48 | ... on NodeTest {
49 | graphqlTestContextualNodeView {
50 | results {
51 | entityId
52 | }
53 | }
54 | }
55 | }
56 | node5:nodeById (id: "1", language: EN) {
57 | ... on NodeTest {
58 | graphqlTestContextualNodeView (contextualFilter: {nid: "X"}) {
59 | results {
60 | entityId
61 | }
62 | }
63 | }
64 | }
65 |
66 | # graphql_test:contextual_nodetest
67 | nodetest0:graphqlTestContextualNodetestView {
68 | results {
69 | entityId
70 | }
71 | }
72 |
73 | nodetest1:graphqlTestContextualNodetestView (contextualFilter: {nid: "X"}) {
74 | results {
75 | entityId
76 | }
77 | }
78 |
79 | nodetest2:nodeById (id: "1", language: EN) {
80 | ... on NodeTest {
81 | graphqlTestContextualNodetestView {
82 | results {
83 | entityId
84 | }
85 | }
86 | }
87 | }
88 |
89 | nodetest3:nodeById (id: "1", language: EN) {
90 | ... on NodeTest {
91 | graphqlTestContextualNodetestView (contextualFilter: {nid: "X"}) {
92 | results {
93 | entityId
94 | }
95 | }
96 | }
97 | }
98 |
99 | # graphql_test:contextual_node_and_nodetest
100 | node_and_nodetest0:graphqlTestContextualNodeAndNodetestView {
101 | results {
102 | entityId
103 | }
104 | }
105 |
106 | node_and_nodetest1:graphqlTestContextualNodeAndNodetestView (contextualFilter: {nid: "X", nid_1: "X"}) {
107 | results {
108 | entityId
109 | }
110 | }
111 |
112 | node_and_nodetest2:nodeById (id: $test2NodeId, language: EN) {
113 | ... on Node {
114 | graphqlTestContextualNodeAndNodetestView {
115 | results {
116 | entityId
117 | }
118 | }
119 | }
120 | }
121 | node_and_nodetest3:nodeById (id: $test2NodeId, language: EN) {
122 | ... on Node {
123 | graphqlTestContextualNodeAndNodetestView (contextualFilter: {nid: "X", nid_1: "X"}) {
124 | results {
125 | entityId
126 | }
127 | }
128 | }
129 | }
130 |
131 | node_and_nodetest4:nodeById (id: "1", language: EN) {
132 | ... on NodeTest {
133 | graphqlTestContextualNodeAndNodetestView {
134 | results {
135 | entityId
136 | }
137 | }
138 | }
139 | }
140 |
141 | node_and_nodetest5:nodeById (id: "1", language: EN) {
142 | ... on NodeTest {
143 | graphqlTestContextualNodeAndNodetestView (contextualFilter: {nid: "X", nid_1: "X"}) {
144 | results {
145 | entityId
146 | }
147 | }
148 | }
149 | }
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/tests/queries/paged.gql:
--------------------------------------------------------------------------------
1 | {
2 | page_one:graphqlTestPagedView {
3 | count
4 | results {
5 | entityLabel
6 | }
7 | }
8 |
9 | page_two:graphqlTestPagedView(page: 1) {
10 | count
11 | results {
12 | entityLabel
13 | }
14 | }
15 |
16 | page_three:graphqlTestPagedView(page: 2, pageSize: 3) {
17 | count
18 | results {
19 | entityLabel
20 | }
21 | }
22 |
23 | page_four:graphqlTestPagedView(page: 2 pageSize: 4) {
24 | count
25 | results {
26 | entityLabel
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/tests/queries/simple.gql:
--------------------------------------------------------------------------------
1 | {
2 | graphqlTestSimpleView {
3 | results {
4 | entityLabel
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/tests/queries/single_bundle_filter.gql:
--------------------------------------------------------------------------------
1 | {
2 | withSingleBundleFilter: graphqlBundleTestGraphql1View {
3 | results {
4 | __typename
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/tests/queries/sorted.gql:
--------------------------------------------------------------------------------
1 | {
2 | default:graphqlTestSortedView {
3 | results {
4 | entityLabel
5 | }
6 | }
7 |
8 | asc:graphqlTestSortedView(sortBy: TITLE) {
9 | results {
10 | entityLabel
11 | }
12 | }
13 |
14 | desc:graphqlTestSortedView(sortBy: TITLE, sortDirection: DESC) {
15 | results {
16 | entityLabel
17 | }
18 | }
19 |
20 | asc_nid:graphqlTestSortedView(sortBy: NID, sortDirection: ASC) {
21 | results {
22 | entityLabel
23 | }
24 | }
25 |
26 | desc_nid:graphqlTestSortedView(sortBy: NID, sortDirection: DESC) {
27 | results {
28 | entityLabel
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/src/Kernel/ContextualViewsTest.php:
--------------------------------------------------------------------------------
1 | createContentType(['type' => 'test2']);
20 | }
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | protected function defaultCacheContexts() {
26 | return array_merge([
27 | 'languages:language_content',
28 | 'languages:language_interface',
29 | 'user.permissions',
30 | 'user.node_grants:view',
31 | ], parent::defaultCacheContexts());
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | protected function defaultCacheTags() {
38 | return array_merge([
39 | 'config:field.storage.node.field_tags',
40 | ], parent::defaultCacheTags());
41 | }
42 |
43 | /**
44 | * Test if view contextual filters are set properly.
45 | */
46 | public function testContextualViewArgs() {
47 | $test2Node = $this->createNode(['type' => 'test2']);
48 |
49 | $this->graphQlProcessor()->processQuery(
50 | $this->getDefaultSchema(),
51 | OperationParams::create([
52 | 'query' => $this->getQueryFromFile('contextual.gql'),
53 | 'variables' => ['test2NodeId' => $test2Node->id()],
54 | ])
55 | );
56 |
57 | $this->assertEquals(drupal_static('graphql_views_test:view:args'), [
58 | 'graphql_test:contextual_title_arg' => [
59 | 0 => [NULL],
60 | 1 => ['X'],
61 | ],
62 | 'graphql_test:contextual_node' => [
63 | 0 => [NULL],
64 | 1 => ['X'],
65 | 2 => ['1'],
66 | 3 => ['X'],
67 | 4 => ['1'],
68 | 5 => ['X'],
69 | ],
70 | 'graphql_test:contextual_nodetest' => [
71 | 0 => [NULL],
72 | 1 => ['X'],
73 | 2 => ['1'],
74 | 3 => ['X'],
75 | ],
76 | 'graphql_test:contextual_node_and_nodetest' => [
77 | 0 => [NULL, NULL],
78 | 1 => ['X', 'X'],
79 | 2 => [$test2Node->id(), NULL],
80 | 3 => ['X', 'X'],
81 | 4 => ['1', '1'],
82 | 5 => ['X', 'X'],
83 | ],
84 | ]);
85 | }
86 |
87 | /**
88 | * Test if view fields are attached to correct types.
89 | */
90 | public function testContextualViewFields() {
91 | $schema = $this->introspect();
92 |
93 | $field = 'graphqlTestContextualTitleArgView';
94 | $this->assertArrayHasKey($field, $schema['types']['Query']['fields']);
95 | $this->assertArrayNotHasKey($field, $schema['types']['Node']['fields']);
96 | $this->assertArrayNotHasKey($field, $schema['types']['NodeTest']['fields']);
97 |
98 | $field = 'graphqlTestContextualNodeView';
99 | $this->assertArrayHasKey($field, $schema['types']['Query']['fields']);
100 | $this->assertArrayHasKey($field, $schema['types']['Node']['fields']);
101 | $this->assertArrayHasKey($field, $schema['types']['NodeTest']['fields']);
102 |
103 | $field = 'graphqlTestContextualNodetestView';
104 | $this->assertArrayHasKey($field, $schema['types']['Query']['fields']);
105 | $this->assertArrayNotHasKey($field, $schema['types']['Node']['fields']);
106 | $this->assertArrayHasKey($field, $schema['types']['NodeTest']['fields']);
107 |
108 | $field = 'graphqlTestContextualNodeAndNodetestView';
109 | $this->assertArrayHasKey($field, $schema['types']['Query']['fields']);
110 | $this->assertArrayHasKey($field, $schema['types']['Node']['fields']);
111 | $this->assertArrayHasKey($field, $schema['types']['NodeTest']['fields']);
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/tests/src/Kernel/ViewsTest.php:
--------------------------------------------------------------------------------
1 | getQueryFromFile('simple.gql');
28 | $this->assertResults($query, [], [
29 | 'graphqlTestSimpleView' => [
30 | 'results' => [
31 | [
32 | 'entityLabel' => 'Node A',
33 | ], [
34 | 'entityLabel' => 'Node B',
35 | ], [
36 | 'entityLabel' => 'Node C',
37 | ],
38 | ],
39 | ],
40 | ], $this->defaultCacheMetaData()->addCacheTags([
41 | 'config:views.view.graphql_test',
42 | 'node:1',
43 | 'node:2',
44 | 'node:3',
45 | 'node_list',
46 | ])->addCacheContexts(['user']));
47 | }
48 |
49 | /**
50 | * Test paging support.
51 | */
52 | public function testPagedView() {
53 | $query = $this->getQueryFromFile('paged.gql');
54 | $this->assertResults($query, [], [
55 | 'page_one' => [
56 | 'count' => count($this->letters),
57 | 'results' => [
58 | ['entityLabel' => 'Node A'],
59 | ['entityLabel' => 'Node B'],
60 | ],
61 | ],
62 | 'page_two' => [
63 | 'count' => count($this->letters),
64 | 'results' => [
65 | ['entityLabel' => 'Node C'],
66 | ['entityLabel' => 'Node A'],
67 | ],
68 | ],
69 | 'page_three' => [
70 | 'count' => count($this->letters),
71 | 'results' => [
72 | ['entityLabel' => 'Node A'],
73 | ['entityLabel' => 'Node B'],
74 | ['entityLabel' => 'Node C'],
75 | ],
76 | ],
77 | 'page_four' => [
78 | 'count' => count($this->letters),
79 | 'results' => [
80 | ['entityLabel' => 'Node C'],
81 | ],
82 | ],
83 | ], $this->defaultCacheMetaData()->addCacheTags([
84 | 'config:views.view.graphql_test',
85 | 'node:1',
86 | 'node:2',
87 | 'node:3',
88 | 'node:4',
89 | 'node:7',
90 | 'node:8',
91 | 'node:9',
92 | 'node_list',
93 | ])->addCacheContexts(['user']));
94 | }
95 |
96 | /**
97 | * Test sorting behavior.
98 | */
99 | public function testSortedView() {
100 | $query = $this->getQueryFromFile('sorted.gql');
101 | $this->assertResults($query, [], [
102 | 'default' => [
103 | 'results' => [
104 | ['entityLabel' => 'Node A'],
105 | ['entityLabel' => 'Node B'],
106 | ['entityLabel' => 'Node C'],
107 | ],
108 | ],
109 | 'asc' => [
110 | 'results' => [
111 | ['entityLabel' => 'Node A'],
112 | ['entityLabel' => 'Node A'],
113 | ['entityLabel' => 'Node A'],
114 | ],
115 | ],
116 | 'desc' => [
117 | 'results' => [
118 | ['entityLabel' => 'Node C'],
119 | ['entityLabel' => 'Node C'],
120 | ['entityLabel' => 'Node C'],
121 | ],
122 | ],
123 | 'asc_nid' => [
124 | 'results' => [
125 | ['entityLabel' => 'Node A'],
126 | ['entityLabel' => 'Node B'],
127 | ['entityLabel' => 'Node C'],
128 | ],
129 | ],
130 | 'desc_nid' => [
131 | 'results' => [
132 | ['entityLabel' => 'Node C'],
133 | ['entityLabel' => 'Node B'],
134 | ['entityLabel' => 'Node A'],
135 | ],
136 | ],
137 | ], $this->defaultCacheMetaData()->addCacheTags([
138 | 'config:views.view.graphql_test',
139 | 'node:1',
140 | 'node:2',
141 | 'node:3',
142 | 'node:4',
143 | 'node:6',
144 | 'node:7',
145 | 'node:8',
146 | 'node:9',
147 | 'node_list',
148 | ])->addCacheContexts(['user']));
149 | }
150 |
151 | /**
152 | * Test filter behavior.
153 | */
154 | public function testFilteredView() {
155 | $query = <<assertResults($query, [], [
167 | 'default' => [
168 | 'results' => [
169 | ['entityLabel' => 'Node A'],
170 | ['entityLabel' => 'Node A'],
171 | ['entityLabel' => 'Node A'],
172 | ],
173 | ],
174 | ], $this->defaultCacheMetaData()->addCacheTags([
175 | 'config:views.view.graphql_test',
176 | 'node:1',
177 | 'node:4',
178 | 'node:7',
179 | 'node_list',
180 | ])->addCacheContexts(['user']));
181 | }
182 |
183 | /**
184 | * Test filter behavior.
185 | */
186 | public function testMultiValueFilteredView() {
187 | $query = <<assertResults($query, [], [
197 | 'multi' => [
198 | 'results' => [
199 | ['entityLabel' => 'Node A'],
200 | ['entityLabel' => 'Node B'],
201 | ['entityLabel' => 'Node A'],
202 | ['entityLabel' => 'Node B'],
203 | ['entityLabel' => 'Node A'],
204 | ['entityLabel' => 'Node B'],
205 | ],
206 | ],
207 | ], $this->defaultCacheMetaData()->addCacheTags([
208 | 'config:views.view.graphql_test',
209 | 'node:1',
210 | 'node:2',
211 | 'node:4',
212 | 'node:5',
213 | 'node:7',
214 | 'node:8',
215 | 'node_list',
216 |
217 | ])->addCacheContexts(['user']));
218 | }
219 |
220 | /**
221 | * Test complex filters.
222 | */
223 | public function testComplexFilteredView() {
224 | $query = <<assertResults($query, [], [
234 | 'complex' => [
235 | 'results' => [
236 | ['entityLabel' => 'Node A'],
237 | ['entityLabel' => 'Node B'],
238 | ['entityLabel' => 'Node C'],
239 | ['entityLabel' => 'Node A'],
240 | ['entityLabel' => 'Node B'],
241 | ['entityLabel' => 'Node C'],
242 | ['entityLabel' => 'Node A'],
243 | ['entityLabel' => 'Node B'],
244 | ['entityLabel' => 'Node C'],
245 | ],
246 | ],
247 | ], $this->defaultCacheMetaData()->addCacheTags([
248 | 'config:views.view.graphql_test',
249 | 'node:1',
250 | 'node:2',
251 | 'node:3',
252 | 'node:4',
253 | 'node:5',
254 | 'node:6',
255 | 'node:7',
256 | 'node:8',
257 | 'node:9',
258 | 'node_list',
259 | ])->addCacheContexts(['user']));
260 | }
261 |
262 | /**
263 | * Test the result type for views with a single-value bundle filter.
264 | */
265 | public function testSingleValueBundleFilterView() {
266 | $query = $this->getQueryFromFile('single_bundle_filter.gql');
267 | $this->assertResults($query, [], [
268 | 'withSingleBundleFilter' => [
269 | 'results' => [
270 | 0 => [
271 | '__typename' => 'NodeTest',
272 | ],
273 | ],
274 | ],
275 | ], $this->defaultCacheMetaData()->addCacheTags([
276 | 'config:views.view.graphql_bundle_test',
277 | 'node:1',
278 | 'node_list',
279 | ]));
280 | }
281 |
282 | }
283 |
--------------------------------------------------------------------------------
/tests/src/Kernel/ViewsTestBase.php:
--------------------------------------------------------------------------------
1 | installEntitySchema('view');
50 | $this->installEntitySchema('taxonomy_term');
51 | $this->installConfig(['node', 'filter', 'views', 'graphql_views_test']);
52 | $this->createEntityReferenceField('node', 'test', 'field_tags', 'Tags', 'taxonomy_term');
53 |
54 | Vocabulary::create([
55 | 'name' => 'Tags',
56 | 'vid' => 'tags',
57 | ])->save();
58 |
59 | $terms = [];
60 |
61 | $terms['A'] = Term::create([
62 | 'name' => 'Term A',
63 | 'vid' => 'tags',
64 | ]);
65 | $terms['A']->save();
66 |
67 | $terms['B'] = Term::create([
68 | 'name' => 'Term B',
69 | 'vid' => 'tags',
70 | ]);
71 | $terms['B']->save();
72 |
73 | $terms['C'] = Term::create([
74 | 'name' => 'Term C',
75 | 'vid' => 'tags',
76 | ]);
77 | $terms['C']->save();
78 |
79 | foreach ($this->letters as $index => $letter) {
80 | $this->createNode([
81 | 'title' => 'Node ' . $letter,
82 | 'type' => 'test',
83 | 'field_tags' => $terms[$letter],
84 | ])->save();
85 | }
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------