Skip to content

Commit 4fc47b9

Browse files
authored
LYNX-100: Add attributeList query to EavGraphQl (#96)
* 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
1 parent f87634a commit 4fc47b9

File tree

4 files changed

+345
-5
lines changed

4 files changed

+345
-5
lines changed

app/code/Magento/EavGraphQl/Model/Output/GetAttributeData.php

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
<?php
2+
23
/**
34
* Copyright © Magento, Inc. All rights reserved.
45
* See COPYING.txt for license details.
56
*/
7+
68
declare(strict_types=1);
79

810
namespace Magento\EavGraphQl\Model\Output;
@@ -69,10 +71,7 @@ public function execute(
6971
'AttributeEntityTypeEnum',
7072
$entityType
7173
),
72-
'frontend_input' => $this->enumLookup->getEnumValueFromField(
73-
'AttributeFrontendInputEnum',
74-
$attribute->getFrontendInput()
75-
),
74+
'frontend_input' => $this->getFrontendInput($attribute),
7675
'is_required' => $attribute->getIsRequired(),
7776
'default_value' => $attribute->getDefaultValue(),
7877
'is_unique' => $attribute->getIsUnique(),
@@ -81,6 +80,23 @@ public function execute(
8180
];
8281
}
8382

83+
/**
84+
* Returns default frontend input for attribute if not set
85+
*
86+
* @param AttributeInterface $attribute
87+
* @return string
88+
*/
89+
private function getFrontendInput(AttributeInterface $attribute): string
90+
{
91+
if ($attribute->getFrontendInput() === null) {
92+
return "UNDEFINED";
93+
}
94+
return $this->enumLookup->getEnumValueFromField(
95+
'AttributeFrontendInputEnum',
96+
$attribute->getFrontendInput()
97+
);
98+
}
99+
84100
/**
85101
* Retrieve formatted attribute options
86102
*
@@ -95,7 +111,11 @@ private function getOptions(AttributeInterface $attribute): array
95111
return array_filter(
96112
array_map(
97113
function (AttributeOptionInterface $option) use ($attribute) {
98-
$value = (string)$option->getValue();
114+
if (is_array($option->getValue())) {
115+
$value = (empty($option->getValue()) ? '' : (string)$option->getValue()[0]['value']);
116+
} else {
117+
$value = (string)$option->getValue();
118+
}
99119
$label = (string)$option->getLabel();
100120
if (empty(trim($value)) && empty(trim($label))) {
101121
return null;
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\EavGraphQl\Model\Resolver;
9+
10+
use Magento\Eav\Model\AttributeRepository;
11+
use Magento\Eav\Api\Data\AttributeInterface;
12+
use Magento\Framework\GraphQl\Query\EnumLookup;
13+
use Magento\Framework\Api\SearchCriteriaBuilder;
14+
use Magento\Framework\GraphQl\Config\Element\Field;
15+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
16+
use Magento\Framework\GraphQl\Query\ResolverInterface;
17+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
18+
use Magento\Framework\Exception\RuntimeException;
19+
use Magento\EavGraphQl\Model\Output\GetAttributeDataInterface;
20+
21+
/**
22+
* Resolve attribute options data for custom attribute.
23+
*/
24+
class AttributesList implements ResolverInterface
25+
{
26+
/**
27+
* @var AttributeRepository
28+
*/
29+
private AttributeRepository $attributeRepository;
30+
31+
/**
32+
* @var GetAttributeDataInterface
33+
*/
34+
private GetAttributeDataInterface $getAttributeData;
35+
36+
/**
37+
* @var SearchCriteriaBuilder
38+
*/
39+
private SearchCriteriaBuilder $searchCriteriaBuilder;
40+
41+
/**
42+
* @var EnumLookup
43+
*/
44+
private EnumLookup $enumLookup;
45+
46+
/**
47+
* @var array
48+
*/
49+
private array $searchCriteriaProviders;
50+
51+
/**
52+
* @param AttributeRepository $attributeRepository
53+
* @param SearchCriteriaBuilder $searchCriteriaBuilder
54+
* @param EnumLookup $enumLookup
55+
* @param GetAttributeDataInterface $getAttributeData
56+
* @param array $searchCriteriaProviders
57+
*/
58+
public function __construct(
59+
AttributeRepository $attributeRepository,
60+
SearchCriteriaBuilder $searchCriteriaBuilder,
61+
EnumLookup $enumLookup,
62+
GetAttributeDataInterface $getAttributeData,
63+
array $searchCriteriaProviders = []
64+
) {
65+
$this->attributeRepository = $attributeRepository;
66+
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
67+
$this->enumLookup = $enumLookup;
68+
$this->getAttributeData = $getAttributeData;
69+
$this->searchCriteriaProviders = $searchCriteriaProviders;
70+
}
71+
72+
/**
73+
* @inheritdoc
74+
*/
75+
public function resolve(
76+
Field $field,
77+
$context,
78+
ResolveInfo $info,
79+
array $value = null,
80+
array $args = null
81+
): array {
82+
if (!$args['entityType']) {
83+
throw new GraphQlInputException(__('Required parameter "%1" of type string.', 'entityType'));
84+
}
85+
86+
$errors = [];
87+
$storeId = (int) $context->getExtensionAttributes()->getStore()->getId();
88+
$entityType = $this->enumLookup->getEnumValueFromField(
89+
'AttributeEntityTypeEnum',
90+
strtolower($args['entityType'])
91+
);
92+
93+
$searchCriteria = $this->searchCriteriaBuilder;
94+
foreach ($this->searchCriteriaProviders as $key => $provider) {
95+
if (!$provider instanceof ResolverInterface) {
96+
throw new RuntimeException(
97+
__('Configured search criteria provider should implement ResolverInterface')
98+
);
99+
}
100+
$searchCriteria->addFilter($key, $provider->resolve($field, $context, $info));
101+
}
102+
$searchCriteria = $searchCriteria->create();
103+
104+
$attributesList = $this->attributeRepository->getList(strtolower($entityType), $searchCriteria)->getItems();
105+
return [
106+
'items' => $this->getAtrributesMetadata($attributesList, $entityType, $storeId),
107+
'errors' => $errors
108+
];
109+
}
110+
111+
/**
112+
* Returns formatted list of attributes
113+
*
114+
* @param AttributeInterface[] $attributesList
115+
* @param string $entityType
116+
* @param int $storeId
117+
*
118+
* @return array[]
119+
*/
120+
private function getAtrributesMetadata(array $attributesList, string $entityType, int $storeId): array
121+
{
122+
return array_map(function (AttributeInterface $attribute) use ($entityType, $storeId): array {
123+
return $this->getAttributeData->execute($attribute, strtolower($entityType), $storeId);
124+
}, $attributesList);
125+
}
126+
}

app/code/Magento/EavGraphQl/etc/schema.graphqls

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ type Query {
55
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")
66
attributesMetadata(input: AttributesMetadataInput!): AttributesMetadataOutput! @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributesMetadata") @doc(description: "Retrieve EAV attributes metadata.")
77
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.")
8+
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)
89
}
910

1011
type CustomAttributeMetadata @doc(description: "Defines an array of custom attributes.") {
@@ -109,6 +110,7 @@ enum AttributeFrontendInputEnum @doc(description: "EAV attribute frontend input
109110
TEXT
110111
TEXTAREA
111112
WEIGHT
113+
UNDEFINED
112114
}
113115

114116
type AttributesFormOutput @doc(description: "Metadata of EAV attributes associated to form") {
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\GraphQl\EavGraphQl;
9+
10+
use Magento\Customer\Api\CustomerMetadataInterface;
11+
use Magento\Catalog\Setup\CategorySetup;
12+
use Magento\Eav\Test\Fixture\Attribute;
13+
use Magento\Eav\Api\Data\AttributeInterface;
14+
use Magento\Sales\Setup\SalesSetup;
15+
use Magento\TestFramework\Fixture\DataFixture;
16+
use Magento\TestFramework\TestCase\GraphQlAbstract;
17+
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
18+
19+
/**
20+
* Test EAV attributes metadata retrieval for entity type via GraphQL API
21+
*/
22+
#[
23+
DataFixture(
24+
Attribute::class,
25+
[
26+
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
27+
'frontend_input' => 'boolean',
28+
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
29+
],
30+
'customerAttribute0'
31+
),
32+
DataFixture(
33+
Attribute::class,
34+
[
35+
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
36+
'frontend_input' => 'boolean',
37+
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
38+
],
39+
'customerAttribute1'
40+
),
41+
DataFixture(
42+
Attribute::class,
43+
[
44+
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
45+
'frontend_input' => 'boolean',
46+
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
47+
],
48+
'customerAttribute2'
49+
),
50+
DataFixture(
51+
Attribute::class,
52+
[
53+
'entity_type_id' => CategorySetup::CATALOG_PRODUCT_ENTITY_TYPE_ID,
54+
'frontend_input' => 'boolean',
55+
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
56+
],
57+
'catalogAttribute3'
58+
),
59+
DataFixture(
60+
Attribute::class,
61+
[
62+
'entity_type_id' => CategorySetup::CATALOG_PRODUCT_ENTITY_TYPE_ID,
63+
'frontend_input' => 'boolean',
64+
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
65+
],
66+
'catalogAttribute4'
67+
),
68+
DataFixture(
69+
Attribute::class,
70+
[
71+
'entity_type_id' => SalesSetup::CREDITMEMO_PRODUCT_ENTITY_TYPE_ID,
72+
'frontend_input' => 'boolean',
73+
'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean'
74+
],
75+
'creditmemoAttribute5'
76+
)
77+
]
78+
class AttributesListTest extends GraphQlAbstract
79+
{
80+
private const ATTRIBUTE_NOT_FOUND_ERROR = "Attribute was not found in query result";
81+
82+
83+
public function testAttributesListForCustomerEntityType(): void
84+
{
85+
$queryResult = $this->graphQlQuery(<<<QRY
86+
{
87+
attributesList(entityType: CUSTOMER) {
88+
items {
89+
uid
90+
code
91+
}
92+
errors {
93+
type
94+
message
95+
}
96+
}
97+
}
98+
QRY);
99+
100+
$this->assertArrayHasKey('items', $queryResult['attributesList'], 'Query result does not contain items');
101+
$this->assertGreaterThanOrEqual(3, count($queryResult['attributesList']['items']));
102+
103+
/** @var AttributeInterface $attribute */
104+
$creditmemoAttribute5 = DataFixtureStorageManager::getStorage()->get('creditmemoAttribute5');
105+
106+
/** @var AttributeInterface $attribute */
107+
$customerAttribute0 = DataFixtureStorageManager::getStorage()->get('customerAttribute0');
108+
/** @var AttributeInterface $attribute */
109+
$customerAttribute1 = DataFixtureStorageManager::getStorage()->get('customerAttribute1');
110+
/** @var AttributeInterface $attribute */
111+
$customerAttribute2 = DataFixtureStorageManager::getStorage()->get('customerAttribute2');
112+
113+
$this->assertEquals(
114+
$customerAttribute0->getAttributeCode(),
115+
$this->getAttributeByCode($queryResult['attributesList']['items'], $customerAttribute0->getAttributeCode())['code'],
116+
self::ATTRIBUTE_NOT_FOUND_ERROR
117+
);
118+
119+
$this->assertEquals(
120+
$customerAttribute1->getAttributeCode(),
121+
$this->getAttributeByCode($queryResult['attributesList']['items'], $customerAttribute1->getAttributeCode())['code'],
122+
self::ATTRIBUTE_NOT_FOUND_ERROR
123+
);
124+
$this->assertEquals(
125+
$customerAttribute2->getAttributeCode(),
126+
$this->getAttributeByCode($queryResult['attributesList']['items'], $customerAttribute2->getAttributeCode())['code'],
127+
self::ATTRIBUTE_NOT_FOUND_ERROR
128+
);
129+
$this->assertEquals(
130+
[],
131+
$this->getAttributeByCode($queryResult['attributesList']['items'], $creditmemoAttribute5->getAttributeCode())
132+
);
133+
}
134+
135+
public function testAttributesListForCatalogProductEntityType(): void
136+
{
137+
$queryResult = $this->graphQlQuery(<<<QRY
138+
{
139+
attributesList(entityType: CATALOG_PRODUCT) {
140+
items {
141+
uid
142+
code
143+
}
144+
errors {
145+
type
146+
message
147+
}
148+
}
149+
}
150+
QRY);
151+
$this->assertArrayHasKey('items', $queryResult['attributesList'], 'Query result does not contain items');
152+
$this->assertGreaterThanOrEqual(2, count($queryResult['attributesList']['items']));
153+
154+
/** @var AttributeInterface $attribute */
155+
$creditmemoAttribute5 = DataFixtureStorageManager::getStorage()->get('creditmemoAttribute5');
156+
157+
/** @var AttributeInterface $attribute */
158+
$catalogAttribute3 = DataFixtureStorageManager::getStorage()->get('catalogAttribute3');
159+
/** @var AttributeInterface $attribute */
160+
$catalogAttribute4 = DataFixtureStorageManager::getStorage()->get('catalogAttribute4');
161+
162+
$this->assertEquals(
163+
$catalogAttribute3->getAttributeCode(),
164+
$this->getAttributeByCode($queryResult['attributesList']['items'], $catalogAttribute3->getAttributeCode())['code'],
165+
self::ATTRIBUTE_NOT_FOUND_ERROR
166+
);
167+
$this->assertEquals(
168+
$catalogAttribute4->getAttributeCode(),
169+
$this->getAttributeByCode($queryResult['attributesList']['items'], $catalogAttribute4->getAttributeCode())['code'],
170+
self::ATTRIBUTE_NOT_FOUND_ERROR
171+
);
172+
$this->assertEquals(
173+
[],
174+
$this->getAttributeByCode($queryResult['attributesList']['items'], $creditmemoAttribute5->getAttributeCode())
175+
);
176+
}
177+
178+
/**
179+
* Finds attribute in query result
180+
*
181+
* @param array $items
182+
* @param string $attribute_code
183+
* @return array
184+
*/
185+
private function getAttributeByCode(array $items, string $attribute_code): array
186+
{
187+
$attribute = array_filter($items, function ($item) use ($attribute_code) {
188+
return $item['code'] == $attribute_code;
189+
});
190+
return $attribute[array_key_first($attribute)] ?? [];
191+
}
192+
}

0 commit comments

Comments
 (0)