Skip to content

Commit 715ee45

Browse files
eug123devpatil7
andauthored
[PWA-2481] [GraphQL] Sort products without custom attribute to end (#18)
* PWA-2481: [GraphQL] Sort products without custom attribute to end * PWA-2481: [GraphQL] Sort products without custom attribute to end * PWA-2481: [GraphQL] Sort products without custom attribute to end * PWA-2481: [GraphQL] Sort products without custom attribute to end * PWA-2481: [GraphQL] Sort products without custom attribute to end * PWA-2481: [GraphQL] Sort products without custom attribute to end Co-authored-by: Devagouda <40405790+dpatil-magento@users.noreply.github.com>
1 parent edf5039 commit 715ee45

File tree

9 files changed

+362
-1
lines changed

9 files changed

+362
-1
lines changed

EavGraphQlAux/Model/Resolver/AttributesMetadata.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Magento\Framework\GraphQl\Query\EnumLookup;
1818
use Magento\EavGraphQl\Model\Resolver\Query\Type;
1919
use Magento\EavGraphQlAux\Model\Resolver\DataProvider\AttributeMetadata as MetadataProvider;
20+
use Magento\Store\Api\Data\StoreInterface;
2021

2122
/**
2223
* @inheritdoc
@@ -92,6 +93,10 @@ public function resolve(
9293
throw new GraphQlInputException(__('Required parameter "entityType" is missing'));
9394
}
9495

96+
/** @var StoreInterface $store */
97+
$store = $context->getExtensionAttributes()->getStore();
98+
$storeId = (int)$store->getId();
99+
95100
$items = [];
96101
$entityType = $this->getEntityType($args['entityType']);
97102

@@ -102,7 +107,7 @@ public function resolve(
102107
);
103108

104109
foreach ($attributes as $attribute) {
105-
$items[] = $this->metadataProvider->getAttributeMetadata($attribute, $entityType);
110+
$items[] = $this->metadataProvider->getAttributeMetadata($attribute, $storeId, $entityType);
106111
}
107112
return [
108113
'items' => $items
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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\EavGraphQlAux\Plugin;
9+
10+
use Magento\Eav\Model\Entity\Attribute\Source\Table;
11+
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
12+
use Magento\Framework\DB\Select;
13+
14+
class ReorderNullAttributeOptions
15+
{
16+
/**
17+
* Plugin to reorder attributes with null value
18+
*
19+
* @param Table $subject
20+
* @param Table $result
21+
* @param AbstractCollection $collection
22+
* @param string $dir
23+
* @return Table
24+
*/
25+
public function afterAddValueSortToCollection(
26+
Table $subject,
27+
Table $result,
28+
AbstractCollection $collection,
29+
string $dir = Select::SQL_ASC
30+
): Table {
31+
$attribute = $subject->getAttribute();
32+
if ($attribute && $attribute->getUsedForSortBy() && $attribute->getIsSearchable()) {
33+
$attributeCode = $subject->getAttribute()->getAttributeCode();
34+
$collection->getSelect()->reset(Select::ORDER);
35+
$collection->getSelect()->order(
36+
new \Zend_Db_Expr("ISNULL({$attributeCode}_value), {$attributeCode}_value {$dir}")
37+
);
38+
}
39+
return $result;
40+
}
41+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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\EavGraphQlAux\Plugin;
9+
10+
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
11+
use Magento\Framework\DB\Select;
12+
13+
class ReorderNullAttributeValues
14+
{
15+
/**
16+
* Plugin to reorder attributes with null value to the bottom
17+
*
18+
* @param AbstractCollection $subject
19+
* @param AbstractCollection $result
20+
* @param string $attribute
21+
* @param string $dir
22+
* @return AbstractCollection
23+
* @throws \Magento\Framework\Exception\LocalizedException
24+
* @throws \Zend_Db_Select_Exception
25+
*/
26+
public function afterAddAttributeToSort(
27+
AbstractCollection $subject,
28+
AbstractCollection $result,
29+
string $attribute,
30+
string $dir = Select::SQL_ASC
31+
): AbstractCollection {
32+
$attributeInstance = $subject->getEntity()->getAttribute($attribute);
33+
if ($attribute !== 'price' &&
34+
$attributeInstance &&
35+
$attributeInstance->getUsedForSortBy() &&
36+
$attributeInstance->getIsSearchable()
37+
) {
38+
$sorOrders = $result->getSelect()->getPart(Select::ORDER);
39+
$orderBy = null;
40+
if (isset($sorOrders[0]) && !$sorOrders[0] instanceof \Zend_Db_Expr) {
41+
$orderBy = $sorOrders[0][0] ?? null;
42+
}
43+
44+
if ($orderBy) {
45+
$result->getSelect()->reset(Select::ORDER);
46+
$result->getSelect()->order(new \Zend_Db_Expr("ISNULL({$orderBy}), {$orderBy} {$dir}"));
47+
}
48+
}
49+
return $result;
50+
}
51+
}

EavGraphQlAux/etc/graphql/di.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,10 @@
5454
</argument>
5555
</arguments>
5656
</type>
57+
<type name="Magento\Eav\Model\Entity\Attribute\Source\Table">
58+
<plugin name="reorder_null_attribute_options" type="Magento\EavGraphQlAux\Plugin\ReorderNullAttributeOptions"/>
59+
</type>
60+
<type name="Magento\Eav\Model\Entity\Collection\AbstractCollection">
61+
<plugin name="reorder_null_attribute_values" type="Magento\EavGraphQlAux\Plugin\ReorderNullAttributeValues"/>
62+
</type>
5763
</config>

QuoteGraphQlPwa/etc/module.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
99
<module name="Magento_QuoteGraphQlPwa">
1010
<sequence>
11+
<module name="Magento_Quote"/>
1112
<module name="Magento_QuoteGraphQl"/>
1213
</sequence>
1314
</module>
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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\EavGraphQlAux;
9+
10+
use Magento\TestFramework\Helper\Bootstrap;
11+
use Magento\TestFramework\ObjectManager;
12+
use Magento\TestFramework\TestCase\GraphQlAbstract;
13+
use Magento\TestFramework\Catalog\Model\GetCategoryByName;
14+
15+
class SortProductsByCustomAttribute extends GraphQlAbstract
16+
{
17+
/** @var ObjectManager */
18+
private $objectManager;
19+
20+
/**
21+
* @inheritdoc
22+
*/
23+
protected function setUp(): void
24+
{
25+
$this->objectManager = Bootstrap::getObjectManager();
26+
}
27+
28+
/**
29+
* Test that product custom attribute basic metadata and selected option are returned
30+
*
31+
* @magentoApiDataFixture Magento/EavGraphQlAux/_files/category_with_three_products_with_attributes.php
32+
* @magentoApiDataFixture Magento/Indexer/_files/reindex_all_invalid.php
33+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
34+
*/
35+
public function testSortByCustomAttribute()
36+
{
37+
/** @var GetCategoryByName $getCategoryByName */
38+
$getCategoryByName = $this->objectManager->create(GetCategoryByName::class);
39+
$categoryId = $getCategoryByName->execute('Category 999')->getId();
40+
41+
// Sort by dropdown_attribute ASC
42+
$queryDropdownASC
43+
= <<<QUERY
44+
{
45+
products(filter: {category_id: {eq: "$categoryId"}}, sort: {dropdown_attribute: ASC}) {
46+
items {
47+
sku
48+
name
49+
}
50+
}
51+
}
52+
53+
QUERY;
54+
$resultDropdownASC = $this->graphQlQuery($queryDropdownASC);
55+
$this->assertArrayNotHasKey('errors', $resultDropdownASC);
56+
$dropdownASC = array_column($resultDropdownASC['products']['items'], 'name');
57+
$expectedDropdownASC = [
58+
'Dropdown: Option 2 / Text: NULL',
59+
'Dropdown: Option 3 / Text: Bb',
60+
'Dropdown: Option NULL / Text: Aa'
61+
];
62+
$this->assertEquals($expectedDropdownASC, $dropdownASC, 'Sort by dropdown_attribute ASC');
63+
64+
// Sort by dropdown_attribute DESC
65+
$queryDropdownDESC
66+
= <<<QUERY
67+
{
68+
products(filter: {category_id: {eq: "$categoryId"}}, sort: {dropdown_attribute: DESC}) {
69+
items {
70+
sku
71+
name
72+
}
73+
}
74+
}
75+
76+
QUERY;
77+
$resultDropdownDESC = $this->graphQlQuery($queryDropdownDESC);
78+
$this->assertArrayNotHasKey('errors', $resultDropdownDESC);
79+
$dropdownDESC = array_column($resultDropdownDESC['products']['items'], 'name');
80+
$expectedDropdownDESC = [
81+
'Dropdown: Option 3 / Text: Bb',
82+
'Dropdown: Option 2 / Text: NULL',
83+
'Dropdown: Option NULL / Text: Aa'
84+
];
85+
$this->assertEquals($expectedDropdownDESC, $dropdownDESC, 'Sort by dropdown_attribute DESC');
86+
87+
// Sort by varchar_attribute ASC
88+
$queryVarcharASC
89+
= <<<QUERY
90+
{
91+
products(filter: {category_id: {eq: "$categoryId"}}, sort: {varchar_attribute: ASC}) {
92+
items {
93+
sku
94+
name
95+
}
96+
}
97+
}
98+
99+
QUERY;
100+
$resultVarcharASC = $this->graphQlQuery($queryVarcharASC);
101+
$this->assertArrayNotHasKey('errors', $resultVarcharASC);
102+
$varcharASC = array_column($resultVarcharASC['products']['items'], 'name');
103+
$expectedVarcharASC = [
104+
'Dropdown: Option NULL / Text: Aa',
105+
'Dropdown: Option 3 / Text: Bb',
106+
'Dropdown: Option 2 / Text: NULL'
107+
];
108+
$this->assertEquals($expectedVarcharASC, $varcharASC, 'Sort by varchar_attribute ASC');
109+
110+
// Sort by varchar_attribute DESC
111+
$queryVarcharDESC
112+
= <<<QUERY
113+
{
114+
products(filter: {category_id: {eq: "$categoryId"}}, sort: {varchar_attribute: DESC}) {
115+
items {
116+
sku
117+
name
118+
}
119+
}
120+
}
121+
122+
QUERY;
123+
$resultVarcharDESC = $this->graphQlQuery($queryVarcharDESC);
124+
$this->assertArrayNotHasKey('errors', $resultVarcharDESC);
125+
$varcharDESC = array_column($resultVarcharDESC['products']['items'], 'name');
126+
$expectedVarcharDESC = [
127+
'Dropdown: Option 3 / Text: Bb',
128+
'Dropdown: Option NULL / Text: Aa',
129+
'Dropdown: Option 2 / Text: NULL'
130+
];
131+
$this->assertEquals($expectedVarcharDESC, $varcharDESC, 'Sort by varchar_attribute DESC');
132+
133+
// Sort by price ASC
134+
$queryPriceASC
135+
= <<<QUERY
136+
{
137+
products(filter: {category_id: {eq: "$categoryId"}}, sort: {price: ASC}) {
138+
items {
139+
sku
140+
name
141+
}
142+
}
143+
}
144+
145+
QUERY;
146+
$resultPriceASC = $this->graphQlQuery($queryPriceASC);
147+
$this->assertArrayNotHasKey('errors', $resultPriceASC);
148+
$priceASC = array_column($resultPriceASC['products']['items'], 'name');
149+
$expectedPriceASC = [
150+
'Dropdown: Option 2 / Text: NULL',
151+
'Dropdown: Option 3 / Text: Bb',
152+
'Dropdown: Option NULL / Text: Aa',
153+
];
154+
$this->assertEquals($expectedPriceASC, $priceASC, 'Sort by Price ASC');
155+
}
156+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
use Magento\Catalog\Api\Data\ProductInterface;
9+
use Magento\Catalog\Api\ProductRepositoryInterface;
10+
use Magento\Eav\Model\Config;
11+
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection;
12+
use Magento\Framework\Api\SearchCriteriaBuilder;
13+
use Magento\TestFramework\Helper\Bootstrap;
14+
use Magento\TestFramework\Workaround\Override\Fixture\Resolver;
15+
16+
Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/category_with_three_products.php');
17+
Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/dropdown_attribute.php');
18+
Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_varchar_attribute.php');
19+
20+
$objectManager = Bootstrap::getObjectManager();
21+
22+
/** @var Config $eavConfig */
23+
$eavConfig = $objectManager->get(Config::class);
24+
$dropdownAttribute = $eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'dropdown_attribute');
25+
$dropdownAttribute
26+
->setIsSearchable('1')
27+
->setUsedForSortBy('1')
28+
->save();
29+
/** @var $options Collection */
30+
$options = $objectManager->create(Collection::class);
31+
$options->setAttributeFilter($dropdownAttribute->getId());
32+
$optionIds = $options->getAllIds();
33+
34+
$varcharAttribute = $eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'varchar_attribute');
35+
$varcharAttribute
36+
->setIsSearchable('1')
37+
->setUsedForSortBy('1')
38+
->save();
39+
40+
/** @var ProductRepositoryInterface $productRepository */
41+
$productRepository = $objectManager->create(ProductRepositoryInterface::class);
42+
/** @var SearchCriteriaBuilder $searchCriteriaBuilder */
43+
$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class);
44+
$searchCriteriaBuilder->addFilter(ProductInterface::SKU, ['simple1000','simple1001','simple1002'], 'in');
45+
$products = $productRepository->getList($searchCriteriaBuilder->create());
46+
47+
foreach ($products->getItems() as $product) {
48+
if ($product->getSku() === 'simple1000') {
49+
$product->setName('Dropdown: Option 2 / Text: NULL');
50+
$product->setDropdownAttribute($optionIds[1]);
51+
}
52+
if ($product->getSku() === 'simple1001') {
53+
$product->setName('Dropdown: Option NULL / Text: Aa');
54+
$product->setVarcharAttribute('Aa');
55+
}
56+
if ($product->getSku() === 'simple1002') {
57+
$product->setName('Dropdown: Option 3 / Text: Bb');
58+
$product->setDropdownAttribute($optionIds[2]);
59+
$product->setVarcharAttribute('Bb');
60+
}
61+
$productRepository->save($product);
62+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\Framework\Exception\NoSuchEntityException;
10+
use Magento\Framework\Registry;
11+
use Magento\TestFramework\Helper\Bootstrap;
12+
use Magento\TestFramework\Workaround\Override\Fixture\Resolver;
13+
14+
$objectManager = Bootstrap::getObjectManager();
15+
$registry = $objectManager->get(Registry::class);
16+
$registry->unregister('isSecureArea');
17+
$registry->register('isSecureArea', true);
18+
19+
$productRepository = $objectManager->get(ProductRepositoryInterface::class);
20+
try {
21+
$productRepository->deleteById('simple1002');
22+
} catch (NoSuchEntityException $e) {
23+
//Already deleted.
24+
}
25+
26+
$registry->unregister('isSecureArea');
27+
$registry->register('isSecureArea', false);
28+
29+
Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/category_with_three_products_rollback.php');
30+
Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/dropdown_attribute_rollback.php');
31+
Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_varchar_attribute_rollback.php');

0 commit comments

Comments
 (0)