Skip to content

Commit

Permalink
LYNX-100: Add attributeList query to EavGraphQl (#96)
Browse files Browse the repository at this point in the history
* LYNX-100: Initial implementation

* LYNX-100: Refa ctoring; GraphQl tests

* LYNX-100: Refactoring

* LYNX-100: Refactoring tests

* LYNX-100: Refactoring; fix WebAPI tests

* LYNX-100: Refactoring

* LYNX-100: Refactoring

* LYNX-100: Refactoring

* LYNX-100: Refactoring

* LYNX-100: CR changes; bugfixing; refactoring

* LYNX-100: Remove whitespaces

* LYNX-100: Fix WebAPI tests

* LYNX-100: Fix WebAPI tests

* LYNX-100: Refactoring; fix static tests

* LYNX-100: Refactoring; bugfixing

* LYNX-100: Remove newlines

* LYNX-100: Fix static tests

* LYNX-100: Return UNDEFINED for frontend_input if not set for attribute

* LYNX-100: Fix static test

* LYNX-100: CR changes

* LYNX-100: CR changes

* LYNX-100: CR changes
  • Loading branch information
bl4de authored Apr 17, 2023
1 parent f87634a commit 4fc47b9
Show file tree
Hide file tree
Showing 4 changed files with 345 additions and 5 deletions.
30 changes: 25 additions & 5 deletions app/code/Magento/EavGraphQl/Model/Output/GetAttributeData.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<?php

/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

declare(strict_types=1);

namespace Magento\EavGraphQl\Model\Output;
Expand Down Expand Up @@ -69,10 +71,7 @@ public function execute(
'AttributeEntityTypeEnum',
$entityType
),
'frontend_input' => $this->enumLookup->getEnumValueFromField(
'AttributeFrontendInputEnum',
$attribute->getFrontendInput()
),
'frontend_input' => $this->getFrontendInput($attribute),
'is_required' => $attribute->getIsRequired(),
'default_value' => $attribute->getDefaultValue(),
'is_unique' => $attribute->getIsUnique(),
Expand All @@ -81,6 +80,23 @@ public function execute(
];
}

/**
* Returns default frontend input for attribute if not set
*
* @param AttributeInterface $attribute
* @return string
*/
private function getFrontendInput(AttributeInterface $attribute): string
{
if ($attribute->getFrontendInput() === null) {
return "UNDEFINED";
}
return $this->enumLookup->getEnumValueFromField(
'AttributeFrontendInputEnum',
$attribute->getFrontendInput()
);
}

/**
* Retrieve formatted attribute options
*
Expand All @@ -95,7 +111,11 @@ private function getOptions(AttributeInterface $attribute): array
return array_filter(
array_map(
function (AttributeOptionInterface $option) use ($attribute) {
$value = (string)$option->getValue();
if (is_array($option->getValue())) {
$value = (empty($option->getValue()) ? '' : (string)$option->getValue()[0]['value']);
} else {
$value = (string)$option->getValue();
}
$label = (string)$option->getLabel();
if (empty(trim($value)) && empty(trim($label))) {
return null;
Expand Down
126 changes: 126 additions & 0 deletions app/code/Magento/EavGraphQl/Model/Resolver/AttributesList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\EavGraphQl\Model\Resolver;

use Magento\Eav\Model\AttributeRepository;
use Magento\Eav\Api\Data\AttributeInterface;
use Magento\Framework\GraphQl\Query\EnumLookup;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\Exception\RuntimeException;
use Magento\EavGraphQl\Model\Output\GetAttributeDataInterface;

/**
* Resolve attribute options data for custom attribute.
*/
class AttributesList implements ResolverInterface
{
/**
* @var AttributeRepository
*/
private AttributeRepository $attributeRepository;

/**
* @var GetAttributeDataInterface
*/
private GetAttributeDataInterface $getAttributeData;

/**
* @var SearchCriteriaBuilder
*/
private SearchCriteriaBuilder $searchCriteriaBuilder;

/**
* @var EnumLookup
*/
private EnumLookup $enumLookup;

/**
* @var array
*/
private array $searchCriteriaProviders;

/**
* @param AttributeRepository $attributeRepository
* @param SearchCriteriaBuilder $searchCriteriaBuilder
* @param EnumLookup $enumLookup
* @param GetAttributeDataInterface $getAttributeData
* @param array $searchCriteriaProviders
*/
public function __construct(
AttributeRepository $attributeRepository,
SearchCriteriaBuilder $searchCriteriaBuilder,
EnumLookup $enumLookup,
GetAttributeDataInterface $getAttributeData,
array $searchCriteriaProviders = []
) {
$this->attributeRepository = $attributeRepository;
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
$this->enumLookup = $enumLookup;
$this->getAttributeData = $getAttributeData;
$this->searchCriteriaProviders = $searchCriteriaProviders;
}

/**
* @inheritdoc
*/
public function resolve(
Field $field,
$context,
ResolveInfo $info,
array $value = null,
array $args = null
): array {
if (!$args['entityType']) {
throw new GraphQlInputException(__('Required parameter "%1" of type string.', 'entityType'));
}

$errors = [];
$storeId = (int) $context->getExtensionAttributes()->getStore()->getId();
$entityType = $this->enumLookup->getEnumValueFromField(
'AttributeEntityTypeEnum',
strtolower($args['entityType'])
);

$searchCriteria = $this->searchCriteriaBuilder;
foreach ($this->searchCriteriaProviders as $key => $provider) {
if (!$provider instanceof ResolverInterface) {
throw new RuntimeException(
__('Configured search criteria provider should implement ResolverInterface')
);
}
$searchCriteria->addFilter($key, $provider->resolve($field, $context, $info));
}
$searchCriteria = $searchCriteria->create();

$attributesList = $this->attributeRepository->getList(strtolower($entityType), $searchCriteria)->getItems();
return [
'items' => $this->getAtrributesMetadata($attributesList, $entityType, $storeId),
'errors' => $errors
];
}

/**
* Returns formatted list of attributes
*
* @param AttributeInterface[] $attributesList
* @param string $entityType
* @param int $storeId
*
* @return array[]
*/
private function getAtrributesMetadata(array $attributesList, string $entityType, int $storeId): array
{
return array_map(function (AttributeInterface $attribute) use ($entityType, $storeId): array {
return $this->getAttributeData->execute($attribute, strtolower($entityType), $storeId);
}, $attributesList);
}
}
2 changes: 2 additions & 0 deletions app/code/Magento/EavGraphQl/etc/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type Query {
customAttributeMetadata(attributes: [AttributeInput!]! @doc(description: "An input object that specifies the attribute code and entity type to search.")): CustomAttributeMetadata @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\CustomAttributeMetadata") @doc(description: "Return the attribute type, given an attribute code and entity type.") @cache(cacheIdentity: "Magento\\EavGraphQl\\Model\\Resolver\\Cache\\CustomAttributeMetadataIdentity")
attributesMetadata(input: AttributesMetadataInput!): AttributesMetadataOutput! @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributesMetadata") @doc(description: "Retrieve EAV attributes metadata.")
attributesForm(type: String! @doc(description: "Form type")): AttributesFormOutput! @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributesForm") @doc(description: "Retrieve EAV attributes associated to a frontend form.")
attributesList(entityType: AttributeEntityTypeEnum! @doc(description: "Entity type.")): AttributesMetadataOutput @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributesList") @doc(description: "Returns list of atributes metadata for given entity type.") @cache(cacheable: false)
}

type CustomAttributeMetadata @doc(description: "Defines an array of custom attributes.") {
Expand Down Expand Up @@ -109,6 +110,7 @@ enum AttributeFrontendInputEnum @doc(description: "EAV attribute frontend input
TEXT
TEXTAREA
WEIGHT
UNDEFINED
}

type AttributesFormOutput @doc(description: "Metadata of EAV attributes associated to form") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\GraphQl\EavGraphQl;

use Magento\Customer\Api\CustomerMetadataInterface;
use Magento\Catalog\Setup\CategorySetup;
use Magento\Eav\Test\Fixture\Attribute;
use Magento\Eav\Api\Data\AttributeInterface;
use Magento\Sales\Setup\SalesSetup;
use Magento\TestFramework\Fixture\DataFixture;
use Magento\TestFramework\TestCase\GraphQlAbstract;
use Magento\TestFramework\Fixture\DataFixtureStorageManager;

/**
* Test EAV attributes metadata retrieval for entity type via GraphQL API
*/
#[
DataFixture(
Attribute::class,
[
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
'frontend_input' => 'boolean',
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
],
'customerAttribute0'
),
DataFixture(
Attribute::class,
[
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
'frontend_input' => 'boolean',
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
],
'customerAttribute1'
),
DataFixture(
Attribute::class,
[
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
'frontend_input' => 'boolean',
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
],
'customerAttribute2'
),
DataFixture(
Attribute::class,
[
'entity_type_id' => CategorySetup::CATALOG_PRODUCT_ENTITY_TYPE_ID,
'frontend_input' => 'boolean',
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
],
'catalogAttribute3'
),
DataFixture(
Attribute::class,
[
'entity_type_id' => CategorySetup::CATALOG_PRODUCT_ENTITY_TYPE_ID,
'frontend_input' => 'boolean',
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
],
'catalogAttribute4'
),
DataFixture(
Attribute::class,
[
'entity_type_id' => SalesSetup::CREDITMEMO_PRODUCT_ENTITY_TYPE_ID,
'frontend_input' => 'boolean',
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
],
'creditmemoAttribute5'
)
]
class AttributesListTest extends GraphQlAbstract
{
private const ATTRIBUTE_NOT_FOUND_ERROR = "Attribute was not found in query result";


public function testAttributesListForCustomerEntityType(): void
{
$queryResult = $this->graphQlQuery(<<<QRY
{
attributesList(entityType: CUSTOMER) {
items {
uid
code
}
errors {
type
message
}
}
}
QRY);

$this->assertArrayHasKey('items', $queryResult['attributesList'], 'Query result does not contain items');
$this->assertGreaterThanOrEqual(3, count($queryResult['attributesList']['items']));

/** @var AttributeInterface $attribute */
$creditmemoAttribute5 = DataFixtureStorageManager::getStorage()->get('creditmemoAttribute5');

/** @var AttributeInterface $attribute */
$customerAttribute0 = DataFixtureStorageManager::getStorage()->get('customerAttribute0');
/** @var AttributeInterface $attribute */
$customerAttribute1 = DataFixtureStorageManager::getStorage()->get('customerAttribute1');
/** @var AttributeInterface $attribute */
$customerAttribute2 = DataFixtureStorageManager::getStorage()->get('customerAttribute2');

$this->assertEquals(
$customerAttribute0->getAttributeCode(),
$this->getAttributeByCode($queryResult['attributesList']['items'], $customerAttribute0->getAttributeCode())['code'],
self::ATTRIBUTE_NOT_FOUND_ERROR
);

$this->assertEquals(
$customerAttribute1->getAttributeCode(),
$this->getAttributeByCode($queryResult['attributesList']['items'], $customerAttribute1->getAttributeCode())['code'],
self::ATTRIBUTE_NOT_FOUND_ERROR
);
$this->assertEquals(
$customerAttribute2->getAttributeCode(),
$this->getAttributeByCode($queryResult['attributesList']['items'], $customerAttribute2->getAttributeCode())['code'],
self::ATTRIBUTE_NOT_FOUND_ERROR
);
$this->assertEquals(
[],
$this->getAttributeByCode($queryResult['attributesList']['items'], $creditmemoAttribute5->getAttributeCode())
);
}

public function testAttributesListForCatalogProductEntityType(): void
{
$queryResult = $this->graphQlQuery(<<<QRY
{
attributesList(entityType: CATALOG_PRODUCT) {
items {
uid
code
}
errors {
type
message
}
}
}
QRY);
$this->assertArrayHasKey('items', $queryResult['attributesList'], 'Query result does not contain items');
$this->assertGreaterThanOrEqual(2, count($queryResult['attributesList']['items']));

/** @var AttributeInterface $attribute */
$creditmemoAttribute5 = DataFixtureStorageManager::getStorage()->get('creditmemoAttribute5');

/** @var AttributeInterface $attribute */
$catalogAttribute3 = DataFixtureStorageManager::getStorage()->get('catalogAttribute3');
/** @var AttributeInterface $attribute */
$catalogAttribute4 = DataFixtureStorageManager::getStorage()->get('catalogAttribute4');

$this->assertEquals(
$catalogAttribute3->getAttributeCode(),
$this->getAttributeByCode($queryResult['attributesList']['items'], $catalogAttribute3->getAttributeCode())['code'],
self::ATTRIBUTE_NOT_FOUND_ERROR
);
$this->assertEquals(
$catalogAttribute4->getAttributeCode(),
$this->getAttributeByCode($queryResult['attributesList']['items'], $catalogAttribute4->getAttributeCode())['code'],
self::ATTRIBUTE_NOT_FOUND_ERROR
);
$this->assertEquals(
[],
$this->getAttributeByCode($queryResult['attributesList']['items'], $creditmemoAttribute5->getAttributeCode())
);
}

/**
* Finds attribute in query result
*
* @param array $items
* @param string $attribute_code
* @return array
*/
private function getAttributeByCode(array $items, string $attribute_code): array
{
$attribute = array_filter($items, function ($item) use ($attribute_code) {
return $item['code'] == $attribute_code;
});
return $attribute[array_key_first($attribute)] ?? [];
}
}

0 comments on commit 4fc47b9

Please sign in to comment.