diff --git a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php index 7a1bbf986de61..bd12a173e0c8d 100644 --- a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php +++ b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php @@ -7,9 +7,49 @@ use Magento\Ui\Controller\Adminhtml\AbstractAction; use Magento\Framework\View\Element\UiComponentInterface; +use Magento\Backend\App\Action\Context; +use Magento\Framework\View\Element\UiComponentFactory; class Render extends AbstractAction { + /** + * @var \Magento\Framework\Controller\Result\JsonFactory + */ + private $resultJsonFactory; + + /** + * @var \Magento\Framework\Escaper + */ + private $escaper; + + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + + /** + * @param Context $context + * @param UiComponentFactory $factory + * @param \Magento\Framework\Controller\Result\JsonFactory|null $resultJsonFactory + * @param \Magento\Framework\Escaper|null $escaper + * @param \Psr\Log\LoggerInterface|null $logger + */ + public function __construct( + Context $context, + UiComponentFactory $factory, + \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory = null, + \Magento\Framework\Escaper $escaper = null, + \Psr\Log\LoggerInterface $logger = null + ) { + parent::__construct($context, $factory); + $this->resultJsonFactory = $resultJsonFactory ?: $this->_objectManager + ->get(\Magento\Framework\Controller\Result\JsonFactory::class); + $this->escaper = $escaper ?: $this->_objectManager + ->get(\Magento\Framework\Escaper::class); + $this->logger = $logger ?: $this->_objectManager + ->get(\Psr\Log\LoggerInterface::class); + } + /** * Action for AJAX request * @@ -22,10 +62,40 @@ public function execute() return; } - $component = $this->factory->create($this->_request->getParam('namespace')); - if ($this->validateAclResource($component->getContext()->getDataProvider()->getConfigData())) { - $this->prepareComponent($component); - $this->_response->appendBody((string) $component->render()); + try { + $component = $this->factory->create($this->_request->getParam('namespace')); + if ($this->validateAclResource($component->getContext()->getDataProvider()->getConfigData())) { + $this->prepareComponent($component); + $this->_response->appendBody((string) $component->render()); + } + } catch (\Magento\Framework\Exception\LocalizedException $e) { + $this->logger->critical($e); + $result = [ + 'error' => $this->escaper->escapeHtml($e->getMessage()), + 'errorcode' => $this->escaper->escapeHtml($e->getCode()) + ]; + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultJsonFactory->create(); + $resultJson->setStatusHeader( + \Zend\Http\Response::STATUS_CODE_400, + \Zend\Http\AbstractMessage::VERSION_11, + 'Bad Request' + ); + return $resultJson->setData($result); + } catch (\Exception $e) { + $this->logger->critical($e); + $result = [ + 'error' => _('UI component could not be rendered because of system exception'), + 'errorcode' => $this->escaper->escapeHtml($e->getCode()) + ]; + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultJsonFactory->create(); + $resultJson->setStatusHeader( + \Zend\Http\Response::STATUS_CODE_400, + \Zend\Http\AbstractMessage::VERSION_11, + 'Bad Request' + ); + return $resultJson->setData($result); } } diff --git a/app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js b/app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js index 22ba19657168d..7748aa94eeec4 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js @@ -10,8 +10,10 @@ define([ 'underscore', 'mageUtils', 'uiLayout', - 'uiCollection' -], function (_, utils, layout, Collection) { + 'uiCollection', + 'mage/translate', + 'jquery' +], function (_, utils, layout, Collection, $t, $) { 'use strict'; /** @@ -48,6 +50,7 @@ define([ stickyTmpl: 'ui/grid/sticky/filters', _processed: [], columnsProvider: 'ns = ${ $.ns }, componentType = columns', + bookmarksProvider: 'ns = ${ $.ns }, componentType = bookmark', applied: { placeholder: true }, @@ -102,7 +105,9 @@ define([ applied: '${ $.provider }:params.filters' }, imports: { - 'onColumnsUpdate': '${ $.columnsProvider }:elems' + onColumnsUpdate: '${ $.columnsProvider }:elems', + onBackendError: '${ $.provider }:lastError', + bookmarksActiveIndex: '${ $.bookmarksProvider }:activeIndex' }, modules: { columns: '${ $.columnsProvider }', @@ -350,6 +355,15 @@ define([ return this.active.length; }, + /** + * Assigns filters index change. + * + * @param {integer} index - Selected index of the filter. + */ + onActiveIndexChange: function (index) { + this.activeIndex = index; + }, + /** * Extract previews of a specified filters. * @@ -371,6 +385,37 @@ define([ */ onColumnsUpdate: function (columns) { columns.forEach(this.addFilter, this); + }, + + /** + * Provider ajax error listener. + * + * @param {bool} isError - Selected index of the filter. + */ + onBackendError: function (isError) { + var defaultMessage = 'Something went wrong with processing the default view and we have restored the' + + ' filter to its original state.', + customMessage = 'Something went wrong with processing current custom view and filters have been' + + ' reset to its original state. Please edit filters then click apply.'; + + if (isError) { + this.clear(); + + $('body').notification('clear') + .notification('add', { + error: true, + message: $.mage.__(this.bookmarksActiveIndex !== 'default' ? customMessage : defaultMessage), + + /** + * @param {String} message + */ + insertMethod: function (message) { + var $wrapper = $('
').html(message); + + $('.page-main-actions').after($wrapper); + } + }); + } } }); }); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/provider.js b/app/code/Magento/Ui/view/base/web/js/grid/provider.js index 14a64216ef597..cc73dc7b07307 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/provider.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/provider.js @@ -120,7 +120,7 @@ define([ request .done(this.onReload) - .fail(this.onError); + .fail(this.onError.bind(this)); return request; }, @@ -144,6 +144,10 @@ define([ return; } + this.set('lastError', true); + + this.firstLoad = false; + alert({ content: $t('Something went wrong.') }); @@ -157,6 +161,8 @@ define([ onReload: function (data) { this.firstLoad = false; + this.set('lastError', false); + this.setData(data) .trigger('reloaded'); }, diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Block/System/Store/StoreGrid.php b/dev/tests/functional/tests/app/Magento/Backend/Test/Block/System/Store/StoreGrid.php index ae3857d8e4f6d..87a3ed048dbb9 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Block/System/Store/StoreGrid.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Block/System/Store/StoreGrid.php @@ -99,6 +99,18 @@ public function searchAndOpenWebsite(Website $website) $this->_rootElement->find(sprintf($this->storeName, $websiteName), Locator::SELECTOR_XPATH)->click(); } + /** + * Search and open appropriate Website by name. + * + * @param string $websiteName + * @return void + */ + public function searchAndOpenWebsiteByName($websiteName) + { + $this->search(['website_title' => $websiteName]); + $this->_rootElement->find(sprintf($this->storeName, $websiteName), Locator::SELECTOR_XPATH)->click(); + } + /** * Search and open appropriate Store View. * diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Grid.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Grid.php index 67ef167e3fcdd..29efb2b4fa037 100755 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Grid.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Grid.php @@ -60,6 +60,10 @@ class Grid extends DataGrid 'selector' => '[name="attribute_set_id"]', 'input' => 'select', ], + 'store_id' => [ + 'selector' => '[name="store_id"]', + 'input' => 'select', + ], ]; /** diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductGridIsRendered.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductGridIsRendered.php new file mode 100644 index 0000000000000..1d5c1e33c94d6 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductGridIsRendered.php @@ -0,0 +1,39 @@ +open()->getProductGrid()->getFirstItemId(); + \PHPUnit_Framework_Assert::assertNotNull( + $productId, + 'Product grid is not rendered correctly.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Product grid is rendered correctly.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertResetFilterMessage.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertResetFilterMessage.php new file mode 100644 index 0000000000000..ed056bf8a998e --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertResetFilterMessage.php @@ -0,0 +1,38 @@ +getMessagesBlock()->getErrorMessage() + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Filters have been reset successfully.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/GridFilteringDeletedEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/GridFilteringDeletedEntityTest.xml new file mode 100644 index 0000000000000..4f7f7fc9f6b96 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/GridFilteringDeletedEntityTest.xml @@ -0,0 +1,30 @@ + + + + + + severity:S2 + + + Magento\Store\Test\TestStep\DeleteWebsitesEntityStep + + + catalogProductSimple + product_with_additional_website + + + :name + + + Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex + getProductGrid + + + + + diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestStep/DeleteWebsitesEntityStep.php b/dev/tests/functional/tests/app/Magento/Store/Test/TestStep/DeleteWebsitesEntityStep.php new file mode 100644 index 0000000000000..c18f5629d2e02 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestStep/DeleteWebsitesEntityStep.php @@ -0,0 +1,122 @@ +storeIndex = $storeIndex; + $this->editWebsite = $editWebsite; + $this->backupIndex = $backupIndex; + $this->deleteWebsite = $deleteWebsite; + $this->item = $item; + $this->createBackup = $createBackup; + $this->fixtureFactory = $fixtureFactory; + } + + /** + * Delete specific Store View. + * + * @return void + */ + public function run() + { + $this->backupIndex->open()->getBackupGrid()->massaction([], 'Delete', true, 'Select All'); + $this->storeIndex->open(); + $websiteNames = $this->item->getWebsiteIds(); + if (is_array($websiteNames) && count($websiteNames) > 0) { + $websiteName = end($websiteNames); + $this->storeIndex->getStoreGrid()->searchAndOpenWebsiteByName($websiteName); + $this->editWebsite->getFormPageActions()->delete(); + $this->deleteWebsite->getDeleteWebsiteForm()->fillForm(['create_backup' => $this->createBackup]); + $this->deleteWebsite->getFormPageActions()->delete(); + } + } +} diff --git a/dev/tests/functional/tests/app/Magento/Ui/Test/TestCase/GridFilteringDeletedEntityTest.php b/dev/tests/functional/tests/app/Magento/Ui/Test/TestCase/GridFilteringDeletedEntityTest.php new file mode 100644 index 0000000000000..6c5d91fda0d9c --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Ui/Test/TestCase/GridFilteringDeletedEntityTest.php @@ -0,0 +1,136 @@ +pageFactory = $pageFactory; + $this->fixtureFactory = $fixtureFactory; + } + + /** + * @param string $pageClass + * @param string $gridRetriever + * @param string[] $filters + * @param string $fixtureName + * @param string[] $steps + * @param array $fixtureDataSet + * @return void + */ + public function test( + $pageClass, + $gridRetriever, + array $filters, + $fixtureName, + array $steps = [], + $fixtureDataSet = null + ) { + $item = $this->createItems($fixtureName, $fixtureDataSet); + $page = $this->pageFactory->create($pageClass); + + $page->open(); + /** @var DataGrid $gridBlock */ + $gridBlock = $page->$gridRetriever(); + $gridBlock->resetFilter(); + + foreach ($filters as $itemFilters) { + $filterArray = []; + foreach ($itemFilters as $itemFiltersName => $itemFilterValue) { + if (substr($itemFilterValue, 0, 1) === ':') { + $value = $item->getData(substr($itemFilterValue, 1)); + } else { + $value = $itemFilterValue; + } + $filterArray[$itemFiltersName] = $value; + } + + $storesArray = $item->getDataFieldConfig('website_ids')['source']->getStores(); + $store = end($storesArray); + $filterArray['store_id'] = $store->getName(); + $gridBlock->search($filterArray); + } + + if (!empty($steps)) { + foreach ($steps as $step) { + $this->processSteps($item, $step); + } + } + } + + /** + * @param string $fixtureName + * @param string $fixtureDataSet + * @return FixtureInterface + */ + private function createItems($fixtureName, $fixtureDataSet) + { + $item = $this->fixtureFactory->createByCode($fixtureName, ['dataset' => $fixtureDataSet]); + $item->persist(); + return $item; + } + + /** + * @param FixtureInterface $item + * @param array $steps + * @return void + */ + private function processSteps(FixtureInterface $item, $steps) + { + foreach ($steps as $step) { + $processStep = $this->objectManager->create($step, ['item' => $item]); + $processStep->run(); + } + } +}