diff --git a/app/code/Magento/BundleGraphQl/Model/BundleProductTypeResolver.php b/app/code/Magento/BundleGraphQl/Model/BundleProductTypeResolver.php
index b904d3f62a748..211d625fbc754 100644
--- a/app/code/Magento/BundleGraphQl/Model/BundleProductTypeResolver.php
+++ b/app/code/Magento/BundleGraphQl/Model/BundleProductTypeResolver.php
@@ -8,19 +8,22 @@
namespace Magento\BundleGraphQl\Model;
use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface;
+use Magento\Bundle\Model\Product\Type as Type;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class BundleProductTypeResolver implements TypeResolverInterface
{
+ const BUNDLE_PRODUCT = 'BundleProduct';
+
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function resolveType(array $data) : string
{
- if (isset($data['type_id']) && $data['type_id'] == 'bundle') {
- return 'BundleProduct';
+ if (isset($data['type_id']) && $data['type_id'] == Type::TYPE_CODE) {
+ return self::BUNDLE_PRODUCT;
}
return '';
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php
index f2634574a2d15..fc5a563c82b4e 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php
@@ -101,11 +101,21 @@ public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId): \Iterato
$collection->addFieldToFilter('level', ['gt' => $level]);
$collection->addFieldToFilter('level', ['lteq' => $level + $depth - self::DEPTH_OFFSET]);
+ $collection->addAttributeToFilter('is_active', 1, "left");
$collection->setOrder('level');
+ $collection->setOrder(
+ 'position',
+ $collection::SORT_ORDER_DESC
+ );
$collection->getSelect()->orWhere(
- $this->metadata->getMetadata(CategoryInterface::class)->getIdentifierField() . ' = ?',
+ $collection->getSelect()
+ ->getConnection()
+ ->quoteIdentifier(
+ 'e.' . $this->metadata->getMetadata(CategoryInterface::class)->getIdentifierField()
+ ) . ' = ?',
$rootCategoryId
);
+
return $collection->getIterator();
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php
index ac8d5709c85b3..3525ccbb6a2d1 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php
@@ -20,6 +20,16 @@ class ExtractDataFromCategoryTree
*/
private $categoryHydrator;
+ /**
+ * @var CategoryInterface
+ */
+ private $iteratingCategory;
+
+ /**
+ * @var int
+ */
+ private $startCategoryFetchLevel = 1;
+
/**
* @param Hydrator $categoryHydrator
*/
@@ -42,14 +52,60 @@ public function execute(\Iterator $iterator): array
/** @var CategoryInterface $category */
$category = $iterator->current();
$iterator->next();
- $nextCategory = $iterator->current();
- $tree[$category->getId()] = $this->categoryHydrator->hydrateCategory($category);
- $tree[$category->getId()]['model'] = $category;
- if ($nextCategory && (int) $nextCategory->getLevel() !== (int) $category->getLevel()) {
- $tree[$category->getId()]['children'] = $this->execute($iterator);
+ $pathElements = explode("/", $category->getPath());
+ if (empty($tree)) {
+ $this->startCategoryFetchLevel = count($pathElements) - 1;
+ }
+ $this->iteratingCategory = $category;
+ $currentLevelTree = $this->explodePathToArray($pathElements, $this->startCategoryFetchLevel);
+ if (empty($tree)) {
+ $tree = $currentLevelTree;
+ }
+ $tree = $this->mergeCategoriesTrees($currentLevelTree, $tree);
+ }
+ return $tree;
+ }
+
+ /**
+ * Merge together complex categories trees
+ *
+ * @param array $tree1
+ * @param array $tree2
+ * @return array
+ */
+ private function mergeCategoriesTrees(array &$tree1, array &$tree2): array
+ {
+ $mergedTree = $tree1;
+ foreach ($tree2 as $currentKey => &$value) {
+ if (is_array($value) && isset($mergedTree[$currentKey]) && is_array($mergedTree[$currentKey])) {
+ $mergedTree[$currentKey] = $this->mergeCategoriesTrees($mergedTree[$currentKey], $value);
+ } else {
+ $mergedTree[$currentKey] = $value;
}
}
+ return $mergedTree;
+ }
+ /**
+ * Recursive method to generate tree for one category path
+ *
+ * @param array $pathElements
+ * @param int $index
+ * @return array
+ */
+ private function explodePathToArray(array $pathElements, int $index): array
+ {
+ $tree = [];
+ $tree[$pathElements[$index]]['id'] = $pathElements[$index];
+ if ($index === count($pathElements) - 1) {
+ $tree[$pathElements[$index]] = $this->categoryHydrator->hydrateCategory($this->iteratingCategory);
+ $tree[$pathElements[$index]]['model'] = $this->iteratingCategory;
+ }
+ $currentIndex = $index;
+ $index++;
+ if (isset($pathElements[$index])) {
+ $tree[$pathElements[$currentIndex]]['children'] = $this->explodePathToArray($pathElements, $index);
+ }
return $tree;
}
}
diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php
index 90ed5cf54892d..36ee00d55339b 100644
--- a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php
+++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php
@@ -124,6 +124,8 @@ private function fetch() : array
$this->attributeMap[$productId][$attribute->getId()]['attribute_code']
= $attribute->getProductAttribute()->getAttributeCode();
$this->attributeMap[$productId][$attribute->getId()]['values'] = $attributeData['options'];
+ $this->attributeMap[$productId][$attribute->getId()]['label']
+ = $attribute->getProductAttribute()->getStoreLabel();
}
return $this->attributeMap;
diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/ConfigurableVariant.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/ConfigurableVariant.php
index a6e39f693b0e5..3e07fecb2ebe7 100644
--- a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/ConfigurableVariant.php
+++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/ConfigurableVariant.php
@@ -71,9 +71,7 @@ public function __construct(
}
/**
- * Fetch and format configurable variants.
- *
- * {@inheritDoc}
+ * @inheritdoc
*/
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
@@ -85,7 +83,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
return $this->valueFactory->create($result);
}
- $this->variantCollection->addParentId((int)$value[$linkField]);
+ $this->variantCollection->addParentProduct($value['model']);
$fields = $this->getProductFields($info);
$matchedFields = $this->attributeCollection->getRequestAttributes($fields);
$this->variantCollection->addEavAttributes($matchedFields);
diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/Variant/Attributes.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/Variant/Attributes.php
index 658e898f09f81..9f8f728a85eaa 100644
--- a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/Variant/Attributes.php
+++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/Variant/Attributes.php
@@ -7,6 +7,8 @@
namespace Magento\ConfigurableProductGraphQl\Model\Resolver\Variant;
+use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
+use Magento\Framework\GraphQl\Query\Resolver\Value;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
@@ -17,9 +19,17 @@
class Attributes implements ResolverInterface
{
/**
+ * @inheritdoc
+ *
* Format product's option data to conform to GraphQL schema
*
- * {@inheritdoc}
+ * @param Field $field
+ * @param ContextInterface $context
+ * @param ResolveInfo $info
+ * @param array|null $value
+ * @param array|null $args
+ * @throws \Exception
+ * @return mixed|Value
*/
public function resolve(
Field $field,
@@ -35,12 +45,12 @@ public function resolve(
$data = [];
foreach ($value['options'] as $option) {
$code = $option['attribute_code'];
- if (!isset($value['product'][$code])) {
+ if (!isset($value['product']['model'][$code])) {
continue;
}
foreach ($option['values'] as $optionValue) {
- if ($optionValue['value_index'] != $value['product'][$code]) {
+ if ($optionValue['value_index'] != $value['product']['model'][$code]) {
continue;
}
$data[] = [
diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php
index 0d86e16574395..9fda4ec0173ec 100644
--- a/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php
+++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php
@@ -9,9 +9,9 @@
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Product;
-use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory;
-use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection as ChildCollection;
use Magento\Catalog\Model\ProductFactory;
+use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection as ChildCollection;
+use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product as DataProvider;
@@ -47,9 +47,9 @@ class Collection
private $metadataPool;
/**
- * @var int[]
+ * @var Product[]
*/
- private $parentIds = [];
+ private $parentProducts = [];
/**
* @var array
@@ -83,19 +83,21 @@ public function __construct(
}
/**
- * Add parent Id to collection filter
+ * Add parent to collection filter
*
- * @param int $id
+ * @param Product $product
* @return void
*/
- public function addParentId(int $id) : void
+ public function addParentProduct(Product $product) : void
{
- if (!in_array($id, $this->parentIds) && !empty($this->childrenMap)) {
+ if (isset($this->parentProducts[$product->getId()])) {
+ return;
+ }
+
+ if (!empty($this->childrenMap)) {
$this->childrenMap = [];
- $this->parentIds[] = $id;
- } elseif (!in_array($id, $this->parentIds)) {
- $this->parentIds[] = $id;
}
+ $this->parentProducts[$product->getId()] = $product;
}
/**
@@ -130,20 +132,23 @@ public function getChildProductsByParentId(int $id) : array
* Fetch all children products from parent id's.
*
* @return array
+ * @throws \Exception
*/
private function fetch() : array
{
- if (empty($this->parentIds) || !empty($this->childrenMap)) {
+ if (empty($this->parentProducts) || !empty($this->childrenMap)) {
return $this->childrenMap;
}
$linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
- foreach ($this->parentIds as $id) {
+ foreach ($this->parentProducts as $product) {
+ $attributeData = $this->getAttributesCodes($product);
/** @var ChildCollection $childCollection */
$childCollection = $this->childCollectionFactory->create();
+ $childCollection->addAttributeToSelect($attributeData);
+
/** @var Product $product */
- $product = $this->productFactory->create();
- $product->setData($linkField, $id);
+ $product->setData($linkField, $product->getId());
$childCollection->setProductFilter($product);
/** @var Product $childProduct */
@@ -160,4 +165,24 @@ private function fetch() : array
return $this->childrenMap;
}
+
+ /**
+ * Get attributes code
+ *
+ * @param \Magento\Catalog\Model\Product $currentProduct
+ * @return array
+ */
+ private function getAttributesCodes(Product $currentProduct): array
+ {
+ $attributeCodes = [];
+ $allowAttributes = $currentProduct->getTypeInstance()->getConfigurableAttributes($currentProduct);
+ foreach ($allowAttributes as $attribute) {
+ $productAttribute = $attribute->getProductAttribute();
+ if (!\in_array($productAttribute->getAttributeCode(), $attributeCodes)) {
+ $attributeCodes[] = $productAttribute->getAttributeCode();
+ }
+ }
+
+ return $attributeCodes;
+ }
}
diff --git a/app/code/Magento/DirectoryGraphQl/Model/Resolver/Countries.php b/app/code/Magento/DirectoryGraphQl/Model/Resolver/Countries.php
new file mode 100644
index 0000000000000..dc788801f3e6a
--- /dev/null
+++ b/app/code/Magento/DirectoryGraphQl/Model/Resolver/Countries.php
@@ -0,0 +1,63 @@
+dataProcessor = $dataProcessor;
+ $this->countryInformationAcquirer = $countryInformationAcquirer;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ $countries = $this->countryInformationAcquirer->getCountriesInfo();
+
+ $output = [];
+ foreach ($countries as $country) {
+ $output[] = $this->dataProcessor->buildOutputDataArray($country, CountryInformationInterface::class);
+ }
+
+ return $output;
+ }
+}
diff --git a/app/code/Magento/DirectoryGraphQl/Model/Resolver/Country.php b/app/code/Magento/DirectoryGraphQl/Model/Resolver/Country.php
new file mode 100644
index 0000000000000..ea39f12a7bcb5
--- /dev/null
+++ b/app/code/Magento/DirectoryGraphQl/Model/Resolver/Country.php
@@ -0,0 +1,67 @@
+dataProcessor = $dataProcessor;
+ $this->countryInformationAcquirer = $countryInformationAcquirer;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ try {
+ $country = $this->countryInformationAcquirer->getCountryInfo($args['id']);
+ } catch (NoSuchEntityException $exception) {
+ throw new GraphQlNoSuchEntityException(__($exception->getMessage()), $exception);
+ }
+
+ return $this->dataProcessor->buildOutputDataArray(
+ $country,
+ CountryInformationInterface::class
+ );
+ }
+}
diff --git a/app/code/Magento/DirectoryGraphQl/Model/Resolver/Currency.php b/app/code/Magento/DirectoryGraphQl/Model/Resolver/Currency.php
new file mode 100644
index 0000000000000..fb2db6c312ac1
--- /dev/null
+++ b/app/code/Magento/DirectoryGraphQl/Model/Resolver/Currency.php
@@ -0,0 +1,59 @@
+dataProcessor = $dataProcessor;
+ $this->currencyInformationAcquirer = $currencyInformationAcquirer;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ return $this->dataProcessor->buildOutputDataArray(
+ $this->currencyInformationAcquirer->getCurrencyInfo(),
+ CurrencyInformationInterface::class
+ );
+ }
+}
diff --git a/app/code/Magento/DirectoryGraphQl/README.md b/app/code/Magento/DirectoryGraphQl/README.md
new file mode 100644
index 0000000000000..1a5c969b39edf
--- /dev/null
+++ b/app/code/Magento/DirectoryGraphQl/README.md
@@ -0,0 +1,4 @@
+# DirectoryGraphQl
+
+**DirectoryGraphQl** provides type and resolver information for the GraphQl module
+to generate directory information endpoints.
diff --git a/app/code/Magento/DirectoryGraphQl/Test/Mftf/README.md b/app/code/Magento/DirectoryGraphQl/Test/Mftf/README.md
new file mode 100644
index 0000000000000..8e2e188c1fe97
--- /dev/null
+++ b/app/code/Magento/DirectoryGraphQl/Test/Mftf/README.md
@@ -0,0 +1,3 @@
+# Directory Graph Ql Functional Tests
+
+The Functional Test Module for **Magento Directory Graph Ql** module.
diff --git a/app/code/Magento/DirectoryGraphQl/composer.json b/app/code/Magento/DirectoryGraphQl/composer.json
new file mode 100644
index 0000000000000..0a81102a92767
--- /dev/null
+++ b/app/code/Magento/DirectoryGraphQl/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "magento/module-directory-graph-ql",
+ "description": "N/A",
+ "type": "magento2-module",
+ "require": {
+ "php": "~7.1.3||~7.2.0",
+ "magento/module-directory": "*",
+ "magento/framework": "*"
+ },
+ "suggest": {
+ "magento/module-graph-ql": "*"
+ },
+ "license": [
+ "OSL-3.0",
+ "AFL-3.0"
+ ],
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "Magento\\DirectoryGraphQl\\": ""
+ }
+ }
+}
diff --git a/app/code/Magento/DirectoryGraphQl/etc/module.xml b/app/code/Magento/DirectoryGraphQl/etc/module.xml
new file mode 100644
index 0000000000000..5d6ec613f36b3
--- /dev/null
+++ b/app/code/Magento/DirectoryGraphQl/etc/module.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls
new file mode 100644
index 0000000000000..40ef6975fad8b
--- /dev/null
+++ b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls
@@ -0,0 +1,37 @@
+# Copyright © Magento, Inc. All rights reserved.
+# See COPYING.txt for license details.
+
+type Query {
+ currency: Currency @resolver(class: "Magento\\DirectoryGraphQl\\Model\\Resolver\\Currency") @doc(description: "The currency query returns information about store currency.")
+ countries: [Country] @resolver(class: "Magento\\DirectoryGraphQl\\Model\\Resolver\\Countries") @doc(description: "The countries query provides information for all countries.")
+ country (id: String): Country @resolver(class: "Magento\\DirectoryGraphQl\\Model\\Resolver\\Country") @doc(description: "The countries query provides information for a single country.")
+}
+
+type Currency {
+ base_currency_code: String
+ base_currency_symbol: String
+ default_display_currecy_code: String
+ default_display_currecy_symbol: String
+ available_currency_codes: [String]
+ exchange_rates: [ExchangeRate]
+}
+
+type ExchangeRate {
+ currency_to: String
+ rate: Float
+}
+
+type Country {
+ id: String
+ two_letter_abbreviation: String
+ three_letter_abbreviation: String
+ full_name_locale: String
+ full_name_english: String
+ available_regions: [Region]
+}
+
+type Region {
+ id: Int
+ code: String
+ name: String
+}
diff --git a/app/code/Magento/DirectoryGraphQl/registration.php b/app/code/Magento/DirectoryGraphQl/registration.php
new file mode 100644
index 0000000000000..6bb7fd8d4e44d
--- /dev/null
+++ b/app/code/Magento/DirectoryGraphQl/registration.php
@@ -0,0 +1,9 @@
+getPurchasedDownloadableProducts = $getPurchasedDownloadableProducts;
+ $this->urlBuilder = $urlBuilder;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ $currentUserId = $context->getUserId();
+ $purchasedProducts = $this->getPurchasedDownloadableProducts->execute($currentUserId);
+ $productsData = [];
+
+ /* The fields names are hardcoded since there's no existing name reference in the code */
+ foreach ($purchasedProducts as $purchasedProduct) {
+ if ($purchasedProduct['number_of_downloads_bought']) {
+ $remainingDownloads = $purchasedProduct['number_of_downloads_bought'] -
+ $purchasedProduct['number_of_downloads_used'];
+ } else {
+ $remainingDownloads = __('Unlimited');
+ }
+
+ $productsData[] = [
+ 'order_increment_id' => $purchasedProduct['order_increment_id'],
+ 'date' => $purchasedProduct['created_at'],
+ 'status' => $purchasedProduct['status'],
+ 'download_url' => $this->urlBuilder->getUrl(
+ 'downloadable/download/link',
+ ['id' => $purchasedProduct['link_hash'], '_secure' => true]
+ ),
+ 'remaining_downloads' => $remainingDownloads
+ ];
+ }
+
+ return ['items' => $productsData];
+ }
+}
diff --git a/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php b/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php
new file mode 100644
index 0000000000000..e8c29e90609f8
--- /dev/null
+++ b/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php
@@ -0,0 +1,58 @@
+resourceConnection = $resourceConnection;
+ }
+
+ /**
+ * Return available purchased products for customer
+ *
+ * @param int $customerId
+ * @return array
+ */
+ public function execute(int $customerId): array
+ {
+ $connection = $this->resourceConnection->getConnection();
+ $allowedItemsStatuses = [Item::LINK_STATUS_PENDING_PAYMENT, Item::LINK_STATUS_PAYMENT_REVIEW];
+ $downloadablePurchasedTable = $connection->getTableName('downloadable_link_purchased');
+
+ /* The fields names are hardcoded since there's no existing name reference in the code */
+ $selectQuery = $connection->select()
+ ->from($downloadablePurchasedTable)
+ ->joinLeft(
+ ['item' => $connection->getTableName('downloadable_link_purchased_item')],
+ "$downloadablePurchasedTable.purchased_id = item.purchased_id"
+ )
+ ->where("$downloadablePurchasedTable.customer_id = ?", $customerId)
+ ->where('item.status NOT IN (?)', $allowedItemsStatuses);
+
+ return $connection->fetchAll($selectQuery);
+ }
+}
diff --git a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls
index 8e877ffe8360a..e2cacdf7608d6 100644
--- a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls
@@ -1,6 +1,22 @@
# Copyright © Magento, Inc. All rights reserved.
# See COPYING.txt for license details.
+type Query {
+ customerDownloadableProducts: CustomerDownloadableProducts @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\CustomerDownloadableProducts") @doc(description: "The query returns the contents of a customer's downloadable products")
+}
+
+type CustomerDownloadableProducts {
+ items: [CustomerDownloadableProduct] @doc(description: "List of purchased downloadable items")
+}
+
+type CustomerDownloadableProduct {
+ order_increment_id: String
+ date: String
+ status: String
+ download_url: String
+ remaining_downloads: String
+}
+
type DownloadableProduct implements ProductInterface, CustomizableProductInterface @doc(description: "DownloadableProduct defines a product that the customer downloads") {
downloadable_product_samples: [DownloadableProductSamples] @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\Product\\DownloadableOptions") @doc(description: "An array containing information about samples of this downloadable product.")
downloadable_product_links: [DownloadableProductLinks] @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\Product\\DownloadableOptions") @doc(description: "An array containing information about the links for this downloadable product")
diff --git a/app/code/Magento/GroupedProductGraphQl/Model/GroupedProductTypeResolver.php b/app/code/Magento/GroupedProductGraphQl/Model/GroupedProductTypeResolver.php
index 087cf10c8d6bb..8818766692fe2 100644
--- a/app/code/Magento/GroupedProductGraphQl/Model/GroupedProductTypeResolver.php
+++ b/app/code/Magento/GroupedProductGraphQl/Model/GroupedProductTypeResolver.php
@@ -8,19 +8,21 @@
namespace Magento\GroupedProductGraphQl\Model;
use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface;
+use Magento\GroupedProduct\Model\Product\Type\Grouped as Type;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class GroupedProductTypeResolver implements TypeResolverInterface
{
+ const GROUPED_PRODUCT = 'GroupedProduct';
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function resolveType(array $data) : string
{
- if (isset($data['type_id']) && $data['type_id'] == 'grouped') {
- return 'GroupedProduct';
+ if (isset($data['type_id']) && $data['type_id'] == Type::TYPE_CODE) {
+ return self::GROUPED_PRODUCT;
}
return '';
}
diff --git a/composer.json b/composer.json
index 17e32e88442e7..7120fe77b0e55 100644
--- a/composer.json
+++ b/composer.json
@@ -143,6 +143,7 @@
"magento/module-developer": "*",
"magento/module-dhl": "*",
"magento/module-directory": "*",
+ "magento/module-directory-graph-ql": "*",
"magento/module-downloadable": "*",
"magento/module-downloadable-graph-ql": "*",
"magento/module-downloadable-import-export": "*",
diff --git a/composer.lock b/composer.lock
index f43bb300bc4ef..48aef8b603d4a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "4b6f2eede23af2202078a4c3043616c6",
+ "content-hash": "f22c780b1ed27ba951c0562e20078a70",
"packages": [
{
"name": "braintree/braintree_php",
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsCountTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsCountTest.php
index eddd456a7b866..46309c6d97dfa 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsCountTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsCountTest.php
@@ -7,11 +7,16 @@
namespace Magento\GraphQl\Catalog;
-use Magento\TestFramework\TestCase\GraphQlAbstract;
+use Magento\Catalog\Api\CategoryLinkManagementInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
-use Magento\TestFramework\Helper\Bootstrap;
-use Magento\Catalog\Model\Product\Visibility;
use Magento\Catalog\Model\Product\Attribute\Source\Status as productStatus;
+use Magento\Catalog\Model\Product\Visibility;
+use Magento\CatalogInventory\Model\Configuration;
+use Magento\Config\Model\ResourceModel\Config;
+use Magento\Framework\App\Config\ReinitableConfigInterface;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\TestFramework\ObjectManager;
+use Magento\TestFramework\TestCase\GraphQlAbstract;
/**
* Class CategoryProductsCountTest
@@ -25,10 +30,39 @@ class CategoryProductsCountTest extends GraphQlAbstract
*/
private $productRepository;
+ /**
+ * @var Config $config
+ */
+ private $resourceConfig;
+
+ /**
+ * @var ScopeConfigInterface
+ */
+ private $scopeConfig;
+
+ /**
+ * @var ReinitableConfigInterface
+ */
+ private $reinitConfig;
+
+ /**
+ * @var CategoryLinkManagementInterface
+ */
+ private $categoryLinkManagement;
+
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
- $objectManager = Bootstrap::getObjectManager();
+ parent::setUp();
+
+ $objectManager = ObjectManager::getInstance();
$this->productRepository = $objectManager->create(ProductRepositoryInterface::class);
+ $this->resourceConfig = $objectManager->get(Config::class);
+ $this->scopeConfig = $objectManager->get(ScopeConfigInterface::class);
+ $this->reinitConfig = $objectManager->get(ReinitableConfigInterface::class);
+ $this->categoryLinkManagement = $objectManager->get(CategoryLinkManagementInterface::class);
}
/**
@@ -82,6 +116,15 @@ public function testCategoryWithInvisibleProduct()
public function testCategoryWithOutOfStockProductManageStockEnabled()
{
$categoryId = 2;
+ $sku = 'simple-out-of-stock';
+ $manageStock = $this->scopeConfig->getValue(Configuration::XML_PATH_MANAGE_STOCK);
+
+ $this->resourceConfig->saveConfig(Configuration::XML_PATH_MANAGE_STOCK, 1);
+ $this->reinitConfig->reinit();
+
+ // need to resave product to reindex it with new configuration.
+ $product = $this->productRepository->get($sku);
+ $this->productRepository->save($product);
$query = <<graphQlQuery($query);
+ $this->resourceConfig->saveConfig(Configuration::XML_PATH_MANAGE_STOCK, $manageStock);
+ $this->reinitConfig->reinit();
+
self::assertEquals(0, $response['category']['product_count']);
}
/**
- * @magentoApiDataFixture Magento/Catalog/_files/category_product.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple_out_of_stock.php
*/
public function testCategoryWithOutOfStockProductManageStockDisabled()
{
- $categoryId = 333;
+ $categoryId = 2;
+ $sku = 'simple-out-of-stock';
+ $manageStock = $this->scopeConfig->getValue(Configuration::XML_PATH_MANAGE_STOCK);
+
+ $this->resourceConfig->saveConfig(Configuration::XML_PATH_MANAGE_STOCK, 0);
+ $this->reinitConfig->reinit();
+
+ // need to resave product to reindex it with new configuration.
+ $product = $this->productRepository->get($sku);
+ $this->productRepository->save($product);
$query = <<graphQlQuery($query);
+ $this->resourceConfig->saveConfig(Configuration::XML_PATH_MANAGE_STOCK, $manageStock);
+ $this->reinitConfig->reinit();
+
self::assertEquals(1, $response['category']['product_count']);
}
@@ -140,4 +198,84 @@ public function testCategoryWithDisabledProduct()
self::assertEquals(0, $response['category']['product_count']);
}
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple_out_of_stock.php
+ */
+ public function testCategoryWithOutOfStockProductShowOutOfStockProduct()
+ {
+ $showOutOfStock = $this->scopeConfig->getValue(Configuration::XML_PATH_SHOW_OUT_OF_STOCK);
+
+ $this->resourceConfig->saveConfig(Configuration::XML_PATH_SHOW_OUT_OF_STOCK, 1);
+ $this->reinitConfig->reinit();
+
+ $categoryId = 2;
+
+ $query = <<graphQlQuery($query);
+
+ $this->resourceConfig->saveConfig(Configuration::XML_PATH_SHOW_OUT_OF_STOCK, $showOutOfStock);
+ $this->reinitConfig->reinit();
+
+ self::assertEquals(1, $response['category']['product_count']);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/CatalogRule/_files/configurable_product.php
+ */
+ public function testCategoryWithConfigurableChildrenOutOfStock()
+ {
+ $categoryId = 2;
+
+ $this->categoryLinkManagement->assignProductToCategories('configurable', [$categoryId]);
+
+ foreach (['simple1', 'simple2'] as $sku) {
+ $product = $this->productRepository->get($sku);
+ $product->setStockData(['is_in_stock' => 0]);
+ $this->productRepository->save($product);
+ }
+
+ $query = <<graphQlQuery($query);
+
+ self::assertEquals(0, $response['category']['product_count']);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/category_product.php
+ */
+ public function testCategoryWithProductNotAvailableOnWebsite()
+ {
+ $product = $this->productRepository->getById(333);
+ $product->setWebsiteIds([]);
+ $this->productRepository->save($product);
+
+ $categoryId = 333;
+
+ $query = <<graphQlQuery($query);
+
+ self::assertEquals(0, $response['category']['product_count']);
+ }
}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php
index 6b57f84e4b9c4..1419aff867d2d 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php
@@ -24,6 +24,7 @@ class CategoryProductsVariantsTest extends GraphQlAbstract
*/
public function testGetSimpleProductsFromCategory()
{
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/360');
$query
= <<objectManager->create(
\Magento\Integration\Api\CustomerTokenServiceInterface::class
);
$customerToken = $customerTokenService->createCustomerAccessToken('customer@example.com', 'password');
-
$headerMap = ['Authorization' => 'Bearer ' . $customerToken];
$response = $this->graphQlQuery($query, [], '', $headerMap);
$responseDataObject = new DataObject($response);
//Some sort of smoke testing
self::assertEquals(
- 'Ololo',
- $responseDataObject->getData('category/children/7/children/1/description')
+ 'Its a description of Test Category 1.2',
+ $responseDataObject->getData('category/children/0/children/1/description')
);
self::assertEquals(
'default-category',
@@ -99,16 +97,52 @@ public function testCategoriesTree()
$responseDataObject->getData('category/children/0/default_sort_by')
);
self::assertCount(
- 8,
+ 7,
$responseDataObject->getData('category/children')
);
self::assertCount(
2,
- $responseDataObject->getData('category/children/7/children')
+ $responseDataObject->getData('category/children/0/children')
+ );
+ self::assertEquals(
+ 13,
+ $responseDataObject->getData('category/children/0/children/1/id')
+ );
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/categories.php
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function testGetCategoryById()
+ {
+ $rootCategoryId = 13;
+ $query = <<objectManager->create(
+ \Magento\Integration\Api\CustomerTokenServiceInterface::class
);
+ $customerToken = $customerTokenService->createCustomerAccessToken('customer@example.com', 'password');
+ $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
+ $response = $this->graphQlQuery($query, [], '', $headerMap);
+ $responseDataObject = new DataObject($response);
+ //Some sort of smoke testing
self::assertEquals(
- 5,
- $responseDataObject->getData('category/children/7/children/1/children/0/id')
+ 'Category 1.2',
+ $responseDataObject->getData('category/name')
+ );
+ self::assertEquals(
+ 13,
+ $responseDataObject->getData('category/id')
);
}
@@ -259,17 +293,14 @@ public function testCategoryProducts()
}
}
QUERY;
-
$response = $this->graphQlQuery($query);
$this->assertArrayHasKey('products', $response['category']);
$this->assertArrayHasKey('total_count', $response['category']['products']);
$this->assertGreaterThanOrEqual(1, $response['category']['products']['total_count']);
$this->assertEquals(1, $response['category']['products']['page_info']['current_page']);
$this->assertEquals(20, $response['category']['products']['page_info']['page_size']);
-
$this->assertArrayHasKey('sku', $response['category']['products']['items'][0]);
$firstProductSku = $response['category']['products']['items'][0]['sku'];
-
/**
* @var ProductRepositoryInterface $productRepository
*/
@@ -279,7 +310,6 @@ public function testCategoryProducts()
$this->assertAttributes($response['category']['products']['items'][0]);
$this->assertWebsites($firstProduct, $response['category']['products']['items'][0]['websites']);
}
-
/**
* @magentoApiDataFixture Magento/Catalog/_files/categories.php
*/
@@ -291,9 +321,7 @@ public function testAnchorCategory()
/** @var CategoryInterface $category */
$category = $categoryCollection->getFirstItem();
$categoryId = $category->getId();
-
$this->assertNotEmpty($categoryId, "Preconditions failed: category is not available.");
-
$query = <<graphQlQuery($query);
$expectedResponse = [
'category' => [
@@ -331,7 +358,6 @@ public function testAnchorCategory()
*/
private function assertBaseFields($product, $actualResponse)
{
-
$assertionMap = [
['response_field' => 'attribute_set_id', 'expected_value' => $product->getAttributeSetId()],
['response_field' => 'created_at', 'expected_value' => $product->getCreatedAt()],
@@ -365,7 +391,6 @@ private function assertBaseFields($product, $actualResponse)
['response_field' => 'type_id', 'expected_value' => $product->getTypeId()],
['response_field' => 'updated_at', 'expected_value' => $product->getUpdatedAt()],
];
-
$this->assertResponseFields($actualResponse, $assertionMap);
}
@@ -385,7 +410,6 @@ private function assertWebsites($product, $actualResponse)
'is_default' => true,
]
];
-
$this->assertEquals($actualResponse, $assertionMap);
}
@@ -410,7 +434,6 @@ private function assertAttributes($actualResponse)
'special_from_date',
'special_to_date',
];
-
foreach ($eavAttributes as $eavAttribute) {
$this->assertArrayHasKey($eavAttribute, $actualResponse);
}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductFrontendLabelAttributeTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductFrontendLabelAttributeTest.php
new file mode 100644
index 0000000000000..32cd3a9a51dcc
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductFrontendLabelAttributeTest.php
@@ -0,0 +1,56 @@
+graphQlQuery($query);
+
+ $this->assertArrayHasKey('products', $response);
+ $this->assertArrayHasKey('items', $response['products']);
+ $this->assertArrayHasKey(0, $response['products']['items']);
+
+ $product = $response['products']['items'][0];
+ $this->assertArrayHasKey('configurable_options', $product);
+ $this->assertArrayHasKey(0, $product['configurable_options']);
+ $this->assertArrayHasKey('label', $product['configurable_options'][0]);
+
+ $option = $product['configurable_options'][0];
+ $this->assertEquals($expectLabelValue, $option['label']);
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php
index 735ae7fff646b..2c2a4a0c60d95 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php
@@ -28,6 +28,8 @@ class ConfigurableProductViewTest extends GraphQlAbstract
*/
public function testQueryConfigurableProductLinks()
{
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/361');
+
$productSku = 'configurable';
$query
@@ -407,6 +409,7 @@ private function assertConfigurableVariants($actualResponse)
$variantArray['product']['price']
);
$configurableOptions = $this->getConfigurableOptions();
+ $this->assertEquals(1, count($variantArray['attributes']));
foreach ($variantArray['attributes'] as $attribute) {
$hasAssertion = false;
foreach ($configurableOptions as $option) {
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CountriesTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CountriesTest.php
new file mode 100644
index 0000000000000..c42ce4c46fa29
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CountriesTest.php
@@ -0,0 +1,45 @@
+graphQlQuery($query);
+ $this->assertArrayHasKey('countries', $result);
+ $this->assertArrayHasKey('id', $result['countries'][0]);
+ $this->assertArrayHasKey('two_letter_abbreviation', $result['countries'][0]);
+ $this->assertArrayHasKey('three_letter_abbreviation', $result['countries'][0]);
+ $this->assertArrayHasKey('full_name_locale', $result['countries'][0]);
+ $this->assertArrayHasKey('full_name_english', $result['countries'][0]);
+ $this->assertArrayHasKey('available_regions', $result['countries'][0]);
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CountryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CountryTest.php
new file mode 100644
index 0000000000000..dda5ef342247d
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CountryTest.php
@@ -0,0 +1,74 @@
+graphQlQuery($query);
+ $this->assertArrayHasKey('country', $result);
+ $this->assertArrayHasKey('id', $result['country']);
+ $this->assertArrayHasKey('two_letter_abbreviation', $result['country']);
+ $this->assertArrayHasKey('three_letter_abbreviation', $result['country']);
+ $this->assertArrayHasKey('full_name_locale', $result['country']);
+ $this->assertArrayHasKey('full_name_english', $result['country']);
+ $this->assertArrayHasKey('available_regions', $result['country']);
+ $this->assertArrayHasKey('id', $result['country']['available_regions'][0]);
+ $this->assertArrayHasKey('code', $result['country']['available_regions'][0]);
+ $this->assertArrayHasKey('name', $result['country']['available_regions'][0]);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage GraphQL response contains errors: The country isn't available.
+ */
+ public function testGetCountryNotFoundException()
+ {
+ $query = <<graphQlQuery($query);
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php
new file mode 100644
index 0000000000000..1ff0b53dda0bb
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php
@@ -0,0 +1,44 @@
+graphQlQuery($query);
+ $this->assertArrayHasKey('currency', $result);
+ $this->assertArrayHasKey('base_currency_code', $result['currency']);
+ $this->assertArrayHasKey('base_currency_symbol', $result['currency']);
+ $this->assertArrayHasKey('default_display_currecy_code', $result['currency']);
+ $this->assertArrayHasKey('default_display_currecy_symbol', $result['currency']);
+ $this->assertArrayHasKey('available_currency_codes', $result['currency']);
+ $this->assertArrayHasKey('exchange_rates', $result['currency']);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php
index a903274793c34..a5ab961932461 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php
@@ -178,7 +178,7 @@
->setParentId(3)
->setPath('1/2/3/13')
->setLevel(3)
- ->setDescription('Ololo')
+ ->setDescription('Its a description of Test Category 1.2')
->setAvailableSortBy('name')
->setDefaultSortBy('name')
->setIsActive(true)
diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute.php
new file mode 100644
index 0000000000000..69607ffb445ba
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute.php
@@ -0,0 +1,22 @@
+get(FrontendLabel::class);
+$frontendLabelAttribute->setStoreId(1);
+$frontendLabelAttribute->setLabel('Default Store View label');
+
+$frontendLabels = $attribute->getFrontendLabels();
+$frontendLabels[] = $frontendLabelAttribute;
+
+$attribute->setFrontendLabels($frontendLabels);
+$attributeRepository->save($attribute);
diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute_rollback.php
new file mode 100644
index 0000000000000..616bd44666efc
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute_rollback.php
@@ -0,0 +1,8 @@
+