diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product.php
index 40aa54fd9387..86137990cc57 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product.php
@@ -7,14 +7,13 @@
namespace Magento\CatalogGraphQl\Model\Resolver;
-use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
+use Magento\CatalogGraphQl\Model\Resolver\Product\ProductFieldsSelector;
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Deferred\Product as ProductDataProvider;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
-use Magento\Framework\GraphQl\Query\FieldTranslator;
-use Magento\Framework\GraphQl\Query\Resolver\Value;
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
use Magento\Framework\GraphQl\Query\ResolverInterface;
+use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
/**
* @inheritdoc
@@ -32,23 +31,23 @@ class Product implements ResolverInterface
private $valueFactory;
/**
- * @var FieldTranslator
+ * @var ProductFieldsSelector
*/
- private $fieldTranslator;
+ private $productFieldsSelector;
/**
* @param ProductDataProvider $productDataProvider
* @param ValueFactory $valueFactory
- * @param FieldTranslator $fieldTranslator
+ * @param ProductFieldsSelector $productFieldsSelector
*/
public function __construct(
ProductDataProvider $productDataProvider,
ValueFactory $valueFactory,
- FieldTranslator $fieldTranslator
+ ProductFieldsSelector $productFieldsSelector
) {
$this->productDataProvider = $productDataProvider;
$this->valueFactory = $valueFactory;
- $this->fieldTranslator = $fieldTranslator;
+ $this->productFieldsSelector = $productFieldsSelector;
}
/**
@@ -60,7 +59,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
throw new GraphQlInputException(__('No child sku found for product link.'));
}
$this->productDataProvider->addProductSku($value['sku']);
- $fields = $this->getProductFields($info);
+ $fields = $this->productFieldsSelector->getProductFieldsFromInfo($info);
$this->productDataProvider->addEavAttributes($fields);
$result = function () use ($value) {
@@ -86,34 +85,4 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
return $this->valueFactory->create($result);
}
-
- /**
- * Return field names for all requested product fields.
- *
- * @param ResolveInfo $info
- * @return string[]
- */
- private function getProductFields(ResolveInfo $info) : array
- {
- $fieldNames = [];
- foreach ($info->fieldNodes as $node) {
- if ($node->name->value !== 'product') {
- continue;
- }
- foreach ($node->selectionSet->selections as $selectionNode) {
- if ($selectionNode->kind === 'InlineFragment') {
- foreach ($selectionNode->selectionSet->selections as $inlineSelection) {
- if ($inlineSelection->kind === 'InlineFragment') {
- continue;
- }
- $fieldNames[] = $this->fieldTranslator->translate($inlineSelection->name->value);
- }
- continue;
- }
- $fieldNames[] = $this->fieldTranslator->translate($selectionNode->name->value);
- }
- }
-
- return $fieldNames;
- }
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductFieldsSelector.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductFieldsSelector.php
new file mode 100644
index 000000000000..9ddad4e6451f
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductFieldsSelector.php
@@ -0,0 +1,61 @@
+fieldTranslator = $fieldTranslator;
+ }
+
+ /**
+ * Return field names for all requested product fields.
+ *
+ * @param ResolveInfo $info
+ * @param string $productNodeName
+ * @return string[]
+ */
+ public function getProductFieldsFromInfo(ResolveInfo $info, string $productNodeName = 'product') : array
+ {
+ $fieldNames = [];
+ foreach ($info->fieldNodes as $node) {
+ if ($node->name->value !== $productNodeName) {
+ continue;
+ }
+ foreach ($node->selectionSet->selections as $selectionNode) {
+ if ($selectionNode->kind === 'InlineFragment') {
+ foreach ($selectionNode->selectionSet->selections as $inlineSelection) {
+ if ($inlineSelection->kind === 'InlineFragment') {
+ continue;
+ }
+ $fieldNames[] = $this->fieldTranslator->translate($inlineSelection->name->value);
+ }
+ continue;
+ }
+ $fieldNames[] = $this->fieldTranslator->translate($selectionNode->name->value);
+ }
+ }
+
+ return $fieldNames;
+ }
+}
diff --git a/app/code/Magento/RelatedProductGraphQl/Model/DataProvider/RelatedProductDataProvider.php b/app/code/Magento/RelatedProductGraphQl/Model/DataProvider/RelatedProductDataProvider.php
new file mode 100644
index 000000000000..173c0a94312e
--- /dev/null
+++ b/app/code/Magento/RelatedProductGraphQl/Model/DataProvider/RelatedProductDataProvider.php
@@ -0,0 +1,78 @@
+linkFactory = $linkFactory;
+ }
+
+ /**
+ * Related Products Data
+ *
+ * @param Product $product
+ * @param array $fields
+ * @param int $linkType
+ * @return array
+ */
+ public function getData(Product $product, array $fields, int $linkType): array
+ {
+ $relatedProducts = $this->getRelatedProducts($product, $fields, $linkType);
+
+ $productsData = [];
+ foreach ($relatedProducts as $relatedProduct) {
+ $productData = $relatedProduct->getData();
+ $productData['model'] = $relatedProduct;
+ $productsData[] = $productData;
+ }
+ return $productsData;
+ }
+
+ /**
+ * Get Related Products
+ *
+ * @param Product $product
+ * @param array $fields
+ * @param int $linkType
+ * @return Product[]
+ */
+ private function getRelatedProducts(Product $product, array $fields, int $linkType): array
+ {
+ /** @var Link $link */
+ $link = $this->linkFactory->create([ 'data' => [
+ 'link_type_id' => $linkType,
+ ]]);
+
+ $collection = $link->getProductCollection();
+ $collection->setIsStrongMode();
+ foreach ($fields as $field) {
+ $collection->addAttributeToSelect($field);
+ }
+ $collection->setProduct($product);
+
+ return $collection->getItems();
+ }
+}
diff --git a/app/code/Magento/RelatedProductGraphQl/Model/Resolver/CrossSellProducts.php b/app/code/Magento/RelatedProductGraphQl/Model/Resolver/CrossSellProducts.php
new file mode 100644
index 000000000000..dcaa75b85f59
--- /dev/null
+++ b/app/code/Magento/RelatedProductGraphQl/Model/Resolver/CrossSellProducts.php
@@ -0,0 +1,59 @@
+productFieldsSelector = $productFieldsSelector;
+ $this->relatedProductDataProvider = $relatedProductDataProvider;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
+ {
+ if (!isset($value['model'])) {
+ throw new LocalizedException(__('"model" value should be specified'));
+ }
+ $product = $value['model'];
+ $fields = $this->productFieldsSelector->getProductFieldsFromInfo($info, 'crosssell_products');
+
+ $data = $this->relatedProductDataProvider->getData($product, $fields, Link::LINK_TYPE_CROSSSELL);
+ return $data;
+ }
+}
diff --git a/app/code/Magento/RelatedProductGraphQl/Model/Resolver/RelatedProducts.php b/app/code/Magento/RelatedProductGraphQl/Model/Resolver/RelatedProducts.php
new file mode 100644
index 000000000000..568c3282a6ee
--- /dev/null
+++ b/app/code/Magento/RelatedProductGraphQl/Model/Resolver/RelatedProducts.php
@@ -0,0 +1,59 @@
+productFieldsSelector = $productFieldsSelector;
+ $this->relatedProductDataProvider = $relatedProductDataProvider;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
+ {
+ if (!isset($value['model'])) {
+ throw new LocalizedException(__('"model" value should be specified'));
+ }
+ $product = $value['model'];
+ $fields = $this->productFieldsSelector->getProductFieldsFromInfo($info, 'related_products');
+
+ $data = $this->relatedProductDataProvider->getData($product, $fields, Link::LINK_TYPE_RELATED);
+ return $data;
+ }
+}
diff --git a/app/code/Magento/RelatedProductGraphQl/Model/Resolver/UpSellProducts.php b/app/code/Magento/RelatedProductGraphQl/Model/Resolver/UpSellProducts.php
new file mode 100644
index 000000000000..df3a62408c53
--- /dev/null
+++ b/app/code/Magento/RelatedProductGraphQl/Model/Resolver/UpSellProducts.php
@@ -0,0 +1,59 @@
+productFieldsSelector = $productFieldsSelector;
+ $this->relatedProductDataProvider = $relatedProductDataProvider;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
+ {
+ if (!isset($value['model'])) {
+ throw new LocalizedException(__('"model" value should be specified'));
+ }
+ $product = $value['model'];
+ $fields = $this->productFieldsSelector->getProductFieldsFromInfo($info, 'upsell_products');
+
+ $data = $this->relatedProductDataProvider->getData($product, $fields, Link::LINK_TYPE_UPSELL);
+ return $data;
+ }
+}
diff --git a/app/code/Magento/RelatedProductGraphQl/README.md b/app/code/Magento/RelatedProductGraphQl/README.md
new file mode 100644
index 000000000000..5b57af5af988
--- /dev/null
+++ b/app/code/Magento/RelatedProductGraphQl/README.md
@@ -0,0 +1,3 @@
+# RelatedProductGraphQl
+
+**RelatedProductGraphQl** provides endpoints for getting Cross Sell / Related/ Up Sell products data.
\ No newline at end of file
diff --git a/app/code/Magento/RelatedProductGraphQl/composer.json b/app/code/Magento/RelatedProductGraphQl/composer.json
new file mode 100644
index 000000000000..0d314d433f17
--- /dev/null
+++ b/app/code/Magento/RelatedProductGraphQl/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "magento/module-related-product-graph-ql",
+ "description": "N/A",
+ "type": "magento2-module",
+ "require": {
+ "php": "~7.1.3||~7.2.0",
+ "magento/module-catalog": "*",
+ "magento/module-catalog-graph-ql": "*",
+ "magento/framework": "*"
+ },
+ "suggest": {
+ "magento/module-graph-ql": "*"
+ },
+ "license": [
+ "OSL-3.0",
+ "AFL-3.0"
+ ],
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "Magento\\RelatedProductGraphQl\\": ""
+ }
+ }
+}
diff --git a/app/code/Magento/RelatedProductGraphQl/etc/module.xml b/app/code/Magento/RelatedProductGraphQl/etc/module.xml
new file mode 100644
index 000000000000..e30775c253a4
--- /dev/null
+++ b/app/code/Magento/RelatedProductGraphQl/etc/module.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/code/Magento/RelatedProductGraphQl/etc/schema.graphqls b/app/code/Magento/RelatedProductGraphQl/etc/schema.graphqls
new file mode 100644
index 000000000000..240fc6c4496f
--- /dev/null
+++ b/app/code/Magento/RelatedProductGraphQl/etc/schema.graphqls
@@ -0,0 +1,8 @@
+# Copyright © Magento, Inc. All rights reserved.
+# See COPYING.txt for license details.
+
+interface ProductInterface {
+ related_products: [ProductInterface] @doc(description: "RelatedProduct") @resolver(class: "Magento\\RelatedProductGraphQl\\Model\\Resolver\\RelatedProducts")
+ upsell_products: [ProductInterface] @doc(description: "RelatedProduct") @resolver(class: "Magento\\RelatedProductGraphQl\\Model\\Resolver\\UpSellProducts")
+ crosssell_products: [ProductInterface] @doc(description: "RelatedProduct") @resolver(class: "Magento\\RelatedProductGraphQl\\Model\\Resolver\\CrossSellProducts")
+}
\ No newline at end of file
diff --git a/app/code/Magento/RelatedProductGraphQl/registration.php b/app/code/Magento/RelatedProductGraphQl/registration.php
new file mode 100644
index 000000000000..d18dfc27cc7c
--- /dev/null
+++ b/app/code/Magento/RelatedProductGraphQl/registration.php
@@ -0,0 +1,9 @@
+graphQlQuery($query);
+
+ self::assertArrayHasKey('products', $response);
+ self::assertArrayHasKey('items', $response['products']);
+ self::assertEquals(1, count($response['products']['items']));
+ self::assertArrayHasKey(0, $response['products']['items']);
+ self::assertArrayHasKey('related_products', $response['products']['items'][0]);
+ $relatedProducts = $response['products']['items'][0]['related_products'];
+ self::assertCount(2, $relatedProducts);
+ self::assertRelatedProducts($relatedProducts);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/products_crosssell.php
+ */
+ public function testQueryCrossSellProducts()
+ {
+ $productSku = 'simple_with_cross';
+
+ $query = <<graphQlQuery($query);
+
+ self::assertArrayHasKey('products', $response);
+ self::assertArrayHasKey('items', $response['products']);
+ self::assertEquals(1, count($response['products']['items']));
+ self::assertArrayHasKey(0, $response['products']['items']);
+ self::assertArrayHasKey('crosssell_products', $response['products']['items'][0]);
+ $crossSellProducts = $response['products']['items'][0]['crosssell_products'];
+ self::assertCount(1, $crossSellProducts);
+ $crossSellProduct = $crossSellProducts[0];
+ self::assertArrayHasKey('sku', $crossSellProduct);
+ self::assertArrayHasKey('name', $crossSellProduct);
+ self::assertArrayHasKey('url_key', $crossSellProduct);
+ self::assertArrayHasKey('created_at', $crossSellProduct);
+ self::assertEquals($crossSellProduct['sku'], 'simple');
+ self::assertEquals($crossSellProduct['name'], 'Simple Cross Sell');
+ self::assertEquals($crossSellProduct['url_key'], 'simple-cross-sell');
+ self::assertNotEmpty($crossSellProduct['created_at']);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/products_upsell.php
+ */
+ public function testQueryUpSellProducts()
+ {
+ $productSku = 'simple_with_upsell';
+
+ $query = <<graphQlQuery($query);
+
+ self::assertArrayHasKey('products', $response);
+ self::assertArrayHasKey('items', $response['products']);
+ self::assertEquals(1, count($response['products']['items']));
+ self::assertArrayHasKey(0, $response['products']['items']);
+ self::assertArrayHasKey('upsell_products', $response['products']['items'][0]);
+ $upSellProducts = $response['products']['items'][0]['upsell_products'];
+ self::assertCount(1, $upSellProducts);
+ $upSellProduct = $upSellProducts[0];
+ self::assertArrayHasKey('sku', $upSellProduct);
+ self::assertArrayHasKey('name', $upSellProduct);
+ self::assertArrayHasKey('url_key', $upSellProduct);
+ self::assertArrayHasKey('created_at', $upSellProduct);
+ self::assertEquals($upSellProduct['sku'], 'simple');
+ self::assertEquals($upSellProduct['name'], 'Simple Up Sell');
+ self::assertEquals($upSellProduct['url_key'], 'simple-up-sell');
+ self::assertNotEmpty($upSellProduct['created_at']);
+ }
+
+ /**
+ * @param array $relatedProducts
+ */
+ private function assertRelatedProducts(array $relatedProducts): void
+ {
+ $expectedData = [
+ 'simple' => [
+ 'name' => 'Simple Related Product',
+ 'url_key' => 'simple-related-product',
+
+ ],
+ 'simple_with_cross_two' => [
+ 'name' => 'Simple Product With Related Product Two',
+ 'url_key' => 'simple-product-with-related-product-two',
+ ]
+ ];
+
+ foreach ($relatedProducts as $product) {
+ self::assertArrayHasKey('sku', $product);
+ self::assertArrayHasKey('name', $product);
+ self::assertArrayHasKey('url_key', $product);
+ self::assertArrayHasKey('created_at', $product);
+
+ self::assertArrayHasKey($product['sku'], $expectedData);
+ $productExpectedData = $expectedData[$product['sku']];
+
+ self::assertEquals($product['name'], $productExpectedData['name']);
+ self::assertEquals($product['url_key'], $productExpectedData['url_key']);
+ self::assertNotEmpty($product['created_at']);
+ }
+ }
+}