-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Add visibility and status filter to category product grid #12564
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add visibility and status filter to category product grid #12564
Conversation
@@ -159,6 +165,30 @@ protected function _prepareColumns() | |||
); | |||
$this->addColumn('name', ['header' => __('Name'), 'index' => 'name']); | |||
$this->addColumn('sku', ['header' => __('SKU'), 'index' => 'sku']); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Static tests are failing due to this: Code must not contain multiple empty lines in a row; found 2 empty lines
'header' => __('Visibility'), | ||
'index' => 'visibility', | ||
'type' => 'options', | ||
'options' => Visibility::getOptionArray(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I had that first but adding it to the constructor also forced me to add it to the constructor of all the classes that extend this class, otherwise it broke. This seemed to me the better solution. Also; why is the method static when we're not allowed to use it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot see any class extending \Magento\Catalog\Block\Adminhtml\Category\Tab\Product
, but as third party modules may have extended this class, there's a preferred way to add a constructor parameter preserving backwards compatibility, as noted in http://devdocs.magento.com/guides/v2.2/contributor-guide/backward-compatible-development/index.html#adding-a-constructor-parameter. It works this way:
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
/**
* Product in category grid
*
* @author Magento Core Team <[email protected]>
*/
namespace Magento\Catalog\Block\Adminhtml\Category\Tab;
use Magento\Backend\Block\Widget\Grid;
use Magento\Backend\Block\Widget\Grid\Column;
use Magento\Backend\Block\Widget\Grid\Extended;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Framework\App\ObjectManager;
class Product extends \Magento\Backend\Block\Widget\Grid\Extended
{
(...)
/**
* @var \Magento\Catalog\Model\Product\Visibility
*/
private $visibility;
/**
* @var \Magento\Catalog\Model\Product\Attribute\Source\Status
*/
private $status;
/**
* @param \Magento\Backend\Block\Template\Context $context
* @param \Magento\Backend\Helper\Data $backendHelper
* @param \Magento\Catalog\Model\ProductFactory $productFactory
* @param \Magento\Framework\Registry $coreRegistry
* @param array $data
* @param Visibility $visibility
* @param Status $status
*/
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Backend\Helper\Data $backendHelper,
\Magento\Catalog\Model\ProductFactory $productFactory,
\Magento\Framework\Registry $coreRegistry,
array $data = [],
\Magento\Catalog\Model\Product\Visibility $visibility = null,
\Magento\Catalog\Model\Product\Attribute\Source\Status $status = null
)
{
$this->visibility = $visibility ?: ObjectManager::getInstance()->get(Visibility::class);
$this->status = $status ?: ObjectManager::getInstance()->get(Status::class);
(...)
}
(...)
/**
* @return Grid
*/
protected function _prepareCollection()
{
if ($this->getCategory()->getId()) {
$this->setDefaultFilter(['in_category' => 1]);
}
$collection = $this->_productFactory->create()->getCollection()->addAttributeToSelect(
'name'
)->addAttributeToSelect(
'sku'
)->addAttributeToSelect(
'price'
)->addAttributeToSelect(
'visibility'
)->addAttributeToSelect(
'status'
)->joinField(
'position',
'catalog_category_product',
'position',
'product_id=entity_id',
'category_id=' . (int)$this->getRequest()->getParam('id', 0),
'left'
);
(...)
}
/**
* @return Extended
*/
protected function _prepareColumns()
{
(...)
$this->addColumn('name', ['header' => __('Name'), 'index' => 'name']);
$this->addColumn('sku', ['header' => __('SKU'), 'index' => 'sku']);
$this->addColumn(
'visibility',
[
'header' => __('Visibility'),
'index' => 'visibility',
'type' => 'options',
'options' => $this->visibility->getOptionArray(),
'header_css_class' => 'col-visibility',
'column_css_class' => 'col-visibility',
]
);
$this->addColumn(
'status',
[
'header' => __('Status'),
'index' => 'status',
'type' => 'options',
'options' => $this->status->getOptionArray(),
]
);
(...)
}
(...)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About using the static method, you may use it that way in this particular case, but the answer about why is prefered constructor param is all about reflection used in object manager, dependency injection, and preferences defined in di.xml. It's more about what class is used than how the method is called.
Constructor params are resolved by the object manager, that may use the preference directive to instantiate one class or another as specified. If you directly include the class you need and use directly its static functions, you are bypassing preferences that may be defined for that class.
An example, let's say I wanted to add a new "dummy" visibility level. An implementation for that (excluding plugins) could be this:
- In di.xml:
<preference for="Magento\Catalog\Model\Product\Visibility" type="\Magento\Catalog\Model\Product\CustomVisibility" />
- new class
\Magento\Catalog\Model\Product\CustomVisibility
:
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Catalog\Model\Product;
use Magento\Framework\Data\OptionSourceInterface;
use Magento\Framework\DB\Ddl\Table;
/**
* Catalog Product visibility model and attribute source model
*
* @api
* @since 100.0.2
*/
class CustomVisibility extends Visibility implements OptionSourceInterface
{
const VISIBILITY_DUMMY = 5;
/**
* Retrieve option array
*
* @return array
*/
public static function getOptionArray()
{
$options = parent::getOptionArray();
$options[self::VISIBILITY_DUMMY] = __('Dummy');
return $options;
}
}
Using the constructor param, \Magento\Catalog\Model\Product\Visibility
is replaced by \Magento\Catalog\Model\Product\CustomVisibility
when it gets injected, and then you can get the options statically or not, as you prefer, but from the right class :
Using directly the class as it's done in the changes suggested in this PR makes Visibility class options irreplaceable, since you are defining directly the class to be called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand why it's nicer to do it through DI, but the question was more; why is that method even static? Something like that should never be static.
And wow, that is one ugly way to add constructor parameter without breaking BC. "Never use object manager, except when you want to preserve BC." Crazy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it shouldn't be static, it's a piece of inherited code from Magento 1, and it should be refactored at some point: https://github.com/magento/magento2/blob/0849373d7636cfa17b70b8188bfc36d13ae749c1/app/code/Magento/Catalog/Model/Product/Visibility.php
About using the object manager, it's true that most of time shouldn't be used directly, but constructor params BC may be one of the few cases where it could make sense, since when the major version is increased and no BC is required, all constructors are refactored to remove this direct object manager usage. From my point of view, where using object manager is absolutely forbidden is in the main logic of any module, if object manager is strictly required to make the module work.
'header' => __('Status'), | ||
'index' => 'status', | ||
'type' => 'options', | ||
'options' => Status::getOptionArray() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as Visibility
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated as per review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this last changes, and squashing commits into a single clean commit with a meaningful message I think we can proceed with this PR
/** | ||
* @var Status | ||
*/ | ||
protected $_status; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New class variables should be private, and should not be prefixed with an underscore to indicate visibility
/** | ||
* @var Visibility | ||
*/ | ||
protected $_visibility; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New class variables should be private, and should not be prefixed with an underscore to indicate visibility
) { | ||
$this->_visibility = $visibility ?: ObjectManager::getInstance()->get(Visibility::class); | ||
$this->_status = $status ?: ObjectManager::getInstance()->get(Status::class); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to rearrange class variable assignations in the same order as defined in constructor, although it's not strictly needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
b157a4d
to
a5bf904
Compare
Thank you very much @peterjaap :D , would you consider to backport this changes? |
@peterjaap @adrian-martinez-interactiv4 I suggest adding some automated tests to this PR as it introduces changes to the behavior. This way it will be compatible with the Magento Core Definition of Done |
@ishakhsuvarov which method would you like to see tests for? |
I do not see to much reason to cover this class or specific method with a unit test, as it would not bring too much value. |
Ok but that feels like an entirely different issue. Besides, shouldn't we want with a functional test until MFTF is done? Seems like duplicate work if we do one now. |
I think integration test is sufficient in this case, it is less challenging to implement and provides sufficient coverage for the specific case. |
@peterjaap @ishakhsuvarov @okorshenko What should we do with this PR? On the one hand, I agree all changes should be covered with tests as possible. On the other hand, this a very simple PR will useful functionality, for an old-fashioned class using old grid system that should be fully refactored and does not seem to be covered by any kind of test yet. I mean, I don't know if given the current testing state for this class, it is worth of writing tests for it, and if tests are not written for this class, we are throwing this useful functionality away. How should we proceed? |
@peterjaap thank you for contributing. Please accept Community Contributors team invitation here to gain extended permissions for this repository. |
1 similar comment
@peterjaap thank you for contributing. Please accept Community Contributors team invitation here to gain extended permissions for this repository. |
Thank you @adrian-martinez-interactiv4 @peterjaap for collaboration. We will cover the fix with the tests on our side |
…ty-and-status-filter-to-category-product-grid
Add visibility and status filter to category product grid to be able to easier filter large amounts of products. Wading through thousands of 'Not visible individually' products is no fun.
Description
Before:

After:


& with filter applied;
Contribution checklist