diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridUrlFilterApplierTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridUrlFilterApplierTest.xml new file mode 100644 index 0000000000000..0565f2d08cc1f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridUrlFilterApplierTest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + <description value="Accessing product grid url with filters parameter"/> + <severity value="MAJOR"/> + <testCaseId value="https://studio.cucumber.io/projects/131313/test-plan/folders/1320712/scenarios/4931106"/> + <group value="product"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <createData entity="simpleProductWithShortNameAndSku" stepKey="createSimpleProduct"/> + </before> + <after> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearGridFilter"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> + </after> + <amOnPage url="{{AdminProductIndexPage.url}}?filters[name]=$$createSimpleProduct.name$$" stepKey="navigateToProductGridWithFilters"/> + <waitForPageLoad stepKey="waitForProductGrid"/> + <see selector="{{AdminProductGridSection.productGridNameProduct($$createSimpleProduct.name$$)}}" userInput="$$createSimpleProduct.name$$" stepKey="seeProduct"/> + <seeElement selector="{{AdminProductGridFilterSection.enabledFilters}}" stepKey="seeEnabledFilters"/> + <see selector="{{AdminProductGridFilterSection.enabledFilters}}" userInput="Name: $$createSimpleProduct.name$$" stepKey="seeProductNameFilter"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_index.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_index.xml index c503196cc8647..89f34a21415d3 100644 --- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_index.xml +++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_index.xml @@ -21,6 +21,7 @@ <referenceContainer name="content"> <uiComponent name="product_listing"/> <block class="Magento\Catalog\Block\Adminhtml\Product" name="products_list"/> + <block class="Magento\Backend\Block\Template" template="Magento_Catalog::product/grid/url_filter_applier.phtml" name="product_list_url_filter_applier" /> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/product/grid/url_filter_applier.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/product/grid/url_filter_applier.phtml new file mode 100644 index 0000000000000..3e00503a882db --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/templates/product/grid/url_filter_applier.phtml @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var $block \Magento\Backend\Block\Template */ +?> +<script type="text/x-magento-init"> + { + "*": { + "Magento_Ui/js/grid/url-filter-applier": { + "listingNamespace": "product_listing" + } + } + } +</script> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/BlockPageActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/BlockPageActionsSection.xml index d487517269c01..ac9c66fe82c74 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/BlockPageActionsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/BlockPageActionsSection.xml @@ -15,8 +15,10 @@ <element name="idColumn" type="button" selector="//div[contains(@data-role, 'grid-wrapper')]/table/thead/tr/th/span[contains(text(), 'ID')]"/> <element name="clearAll" type="button" selector="//div[@class='admin__data-grid-header']//button[contains(text(), 'Clear all')]"/> <element name="activeFilters" type="button" selector="//div[@class='admin__data-grid-header']//span[contains(text(), 'Active filters:')]" /> + <element name="activeFilterDiv" type="button" selector="(//div[contains(@class, 'admin__data-grid-filters-current') and contains(@class, '_show')])[1]"/> <element name="FilterBtn" type="input" selector="//button[text()='Filters']"/> <element name="URLKey" type="input" selector="//div[@class='admin__form-field-control']/input[@name='identifier']"/> <element name="ApplyFiltersBtn" type="button" selector="//span[text()='Apply Filters']"/> + <element name="blockGridRowByTitle" type="input" selector="//tbody//tr//td//div[contains(., '{{var1}}')]" parameterized="true" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml index ebf024490cce6..0d618cf82ce6d 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml @@ -19,6 +19,7 @@ <element name="edit" type="button" selector="//div[text()='{{var1}}']/parent::td//following-sibling::td[@class='data-grid-actions-cell']//a[text()='Edit']" parameterized="true"/> <element name="preview" type="button" selector="//div[text()='{{var1}}']/parent::td//following-sibling::td[@class='data-grid-actions-cell']//a[text()='View']" parameterized="true"/> <element name="clearAllButton" type="button" selector="//div[@class='admin__data-grid-header']//button[contains(text(), 'Clear all')]"/> + <element name="clearFilters" type="button" selector=".admin__data-grid-header button[data-action='grid-filter-reset']" timeout="30"/> <element name="activeFilters" type="button" selector="//div[@class='admin__data-grid-header']//span[contains(text(), 'Active filters:')]" /> <element name="spinner" type="input" selector='//div[@data-component="cms_page_listing.cms_page_listing.cms_page_columns"]'/> <element name="firstItemSelectButton" type="button" selector=".data-grid .action-select-wrap button.action-select"/> @@ -31,5 +32,6 @@ <element name="massActionsButton" type="button" selector="//div[@class='admin__data-grid-header'][(not(ancestor::*[@class='sticky-header']) and not(contains(@style,'visibility: hidden'))) or (ancestor::*[@class='sticky-header' and not(contains(@style,'display: none'))])]//button[contains(@class, 'action-select')]" /> <element name="massActionsOption" type="button" selector="//div[@class='admin__data-grid-header'][(not(ancestor::*[@class='sticky-header']) and not(contains(@style,'visibility: hidden'))) or (ancestor::*[@class='sticky-header' and not(contains(@style,'display: none'))])]//span[contains(@class, 'action-menu-item') and .= '{{action}}']" parameterized="true"/> <element name="gridDataRow" type="input" selector=".data-row .data-grid-cell-content"/> + <element name="pagesGridRowByTitle" type="input" selector="//tbody//tr//td//div[contains(., '{{var1}}')]" parameterized="true" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsBlockGridUrlFilterApplierTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsBlockGridUrlFilterApplierTest.xml new file mode 100644 index 0000000000000..e2cf9c20627f8 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsBlockGridUrlFilterApplierTest.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCmsBlockGridUrlFilterApplierTest"> + <annotations> + <features value="Cms"/> + <stories value="Filter CMS block using GET URL parameter"/> + <title value="Verify that filter is applied on block grid when filters parameter is set on url"/> + <description value="Accessing block grid url with filters parameter"/> + <severity value="MAJOR"/> + <testCaseId value="https://studio.cucumber.io/projects/131313/test-plan/folders/1320712/scenarios/4931106"/> + <group value="Cms"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <createData entity="Sales25offBlock" stepKey="createBlock"/> + </before> + <after> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearGridFilter"/> + <deleteData createDataKey="createBlock" stepKey="deletePage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> + </after> + <amOnPage url="{{CmsBlocksPage.url}}?filters[title]=$$createBlock.title$$" stepKey="navigateToBlockGridWithFilters"/> + <waitForPageLoad stepKey="waitForBlockGrid"/> + <see selector="{{BlockPageActionsSection.blockGridRowByTitle($$createBlock.title$$)}}" userInput="$$createBlock.title$$" stepKey="seeBlock"/> + <seeElement selector="{{BlockPageActionsSection.activeFilterDiv}}" stepKey="seeEnabledFilters"/> + <see selector="{{BlockPageActionsSection.activeFilterDiv}}" userInput="Title: $$createBlock.title$$" stepKey="seeBlockTitleFilter"/> + </test> +</tests> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsPageGridUrlFilterApplierTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsPageGridUrlFilterApplierTest.xml new file mode 100644 index 0000000000000..1f1f1c98d507b --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCmsPageGridUrlFilterApplierTest.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCmsPageGridUrlFilterApplierTest"> + <annotations> + <features value="CmsPage"/> + <stories value="Filter CMS page using GET URL parameter"/> + <title value="Verify that filter is applied on page grid when filters parameter is set on url"/> + <description value="Accessing page grid url with filters parameter"/> + <severity value="MAJOR"/> + <testCaseId value="https://studio.cucumber.io/projects/131313/test-plan/folders/1320712/scenarios/4931106"/> + <group value="Cms"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <createData entity="_defaultCmsPage" stepKey="createPage"/> + </before> + <after> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearGridFilter"/> + <deleteData createDataKey="createPage" stepKey="deletePage"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> + </after> + <amOnPage url="{{CmsPagesPage.url}}?filters[title]=$$createPage.title$$" stepKey="navigateToPageGridWithFilters"/> + <waitForPageLoad stepKey="waitForPageGrid"/> + <see selector="{{CmsPagesPageActionsSection.pagesGridRowByTitle($$createPage.title$$)}}" userInput="$$createPage.title$$" stepKey="seePage"/> + <seeElement selector="{{CmsPagesPageActionsSection.activeFilter}}" stepKey="seeEnabledFilters"/> + <see selector="{{CmsPagesPageActionsSection.activeFilter}}" userInput="Title: $$createPage.title$$" stepKey="seePageTitleFilter"/> + </test> +</tests> diff --git a/app/code/Magento/Cms/view/adminhtml/layout/cms_block_index.xml b/app/code/Magento/Cms/view/adminhtml/layout/cms_block_index.xml index d22eaf504e703..4a8002d89726d 100644 --- a/app/code/Magento/Cms/view/adminhtml/layout/cms_block_index.xml +++ b/app/code/Magento/Cms/view/adminhtml/layout/cms_block_index.xml @@ -9,6 +9,11 @@ <body> <referenceContainer name="content"> <uiComponent name="cms_block_listing"/> + <block class="Magento\Backend\Block\Template" template="Magento_Cms::url_filter_applier.phtml" name="block_list_url_filter_applier"> + <arguments> + <argument name="listing_namespace" xsi:type="string">cms_block_listing</argument> + </arguments> + </block> </referenceContainer> <referenceContainer name="admin.scope.col.wrap" htmlClass="admin__old" /> <!-- ToDo UI: remove this wrapper with old styles removal. The class name "admin__old" is for tests only, we shouldn't use it in any way --> </body> diff --git a/app/code/Magento/Cms/view/adminhtml/layout/cms_page_index.xml b/app/code/Magento/Cms/view/adminhtml/layout/cms_page_index.xml index bf78b1cd49448..1256751e65742 100644 --- a/app/code/Magento/Cms/view/adminhtml/layout/cms_page_index.xml +++ b/app/code/Magento/Cms/view/adminhtml/layout/cms_page_index.xml @@ -10,6 +10,11 @@ <body> <referenceContainer name="content"> <uiComponent name="cms_page_listing"/> + <block class="Magento\Backend\Block\Template" template="Magento_Cms::url_filter_applier.phtml" name="page_list_url_filter_applier"> + <arguments> + <argument name="listing_namespace" xsi:type="string">cms_page_listing</argument> + </arguments> + </block> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Cms/view/adminhtml/templates/url_filter_applier.phtml b/app/code/Magento/Cms/view/adminhtml/templates/url_filter_applier.phtml new file mode 100644 index 0000000000000..a4918e86715a8 --- /dev/null +++ b/app/code/Magento/Cms/view/adminhtml/templates/url_filter_applier.phtml @@ -0,0 +1,18 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var $block \Magento\Backend\Block\Template */ +/** @var \Magento\Framework\Escaper $escaper */ +?> +<script type="text/x-magento-init"> + { + "*": { + "Magento_Ui/js/grid/url-filter-applier": { + "listingNamespace": "<?= $escaper->escapeJs($block->getListingNamespace()) ?>" + } + } + } +</script> diff --git a/app/code/Magento/Ui/view/base/web/js/grid/url-filter-applier.js b/app/code/Magento/Ui/view/base/web/js/grid/url-filter-applier.js new file mode 100644 index 0000000000000..83220028a72df --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/js/grid/url-filter-applier.js @@ -0,0 +1,80 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'uiComponent', + 'underscore' +], function (Component, _) { + 'use strict'; + + return Component.extend({ + defaults: { + listingNamespace: null, + filterProvider: 'componentType = filters, ns = ${ $.listingNamespace }', + filterKey: 'filters', + searchString: location.search, + modules: { + filterComponent: '${ $.filterProvider }' + } + }, + + /** + * Init component + * + * @return {exports} + */ + initialize: function () { + this._super(); + this.apply(); + + return this; + }, + + /** + * Apply filter + */ + apply: function () { + var urlFilter = this.getFilterParam(this.searchString); + + if (_.isUndefined(this.filterComponent())) { + setTimeout(function () { + this.apply(); + }.bind(this), 100); + + return; + } + + if (Object.keys(urlFilter).length) { + this.filterComponent().setData(urlFilter, false); + this.filterComponent().apply(); + } + }, + + /** + * Get filter param from url + * + * @returns {Object} + */ + getFilterParam: function (url) { + var searchString = decodeURI(url), + itemArray; + + return _.chain(searchString.slice(1).split('&')) + .map(function (item) { + if (item && item.search(this.filterKey) !== -1) { + itemArray = item.split('='); + + itemArray[0] = itemArray[0].replace(this.filterKey, '') + .replace(/[\[\]]/g, ''); + + return itemArray; + } + }.bind(this)) + .compact() + .object() + .value(); + } + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/url-filter-applier.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/url-filter-applier.test.js new file mode 100644 index 0000000000000..fb37c73abdd41 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/url-filter-applier.test.js @@ -0,0 +1,67 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/*eslint max-nested-callbacks: 0*/ +define([ + 'Magento_Ui/js/grid/url-filter-applier' +], function (UrlFilterApplier) { + 'use strict'; + + describe('Magento_Ui/js/grid/url-filter-applier', function () { + var urlFilterApplierObj, + filterComponentMock = { + setData: jasmine.createSpy(), + apply: jasmine.createSpy() + }; + + beforeEach(function () { + urlFilterApplierObj = new UrlFilterApplier({}); + urlFilterApplierObj.filterComponent = jasmine.createSpy().and.returnValue(filterComponentMock); + }); + + describe('"getFilterParam" method', function () { + it('return object from url with a simple filters parameter', function () { + var urlSearch = '?filters[name]=test'; + + expect(urlFilterApplierObj.getFilterParam(urlSearch)).toEqual({ + 'name': 'test' + }); + }); + it('return object from url with multiple filters parameter', function () { + var urlSearch = '?filters[name]=test&filters[qty]=1'; + + expect(urlFilterApplierObj.getFilterParam(urlSearch)).toEqual({ + 'name': 'test', + 'qty': '1' + }); + }); + it('return object from url with multiple filters parameter and another parameter', function () { + var urlSearch = '?filters[name]=test&filters[qty]=1&anotherparam=1'; + + expect(urlFilterApplierObj.getFilterParam(urlSearch)).toEqual({ + 'name': 'test', + 'qty': '1' + }); + }); + it('return object from url with another parameter', function () { + var urlSearch = '?anotherparam=1'; + + expect(urlFilterApplierObj.getFilterParam(urlSearch)).toEqual({}); + }); + }); + + describe('"apply" method', function () { + it('applies url filter on filter component', function () { + urlFilterApplierObj.searchString = '?filters[name]=test&filters[qty]=1'; + urlFilterApplierObj.apply(); + expect(urlFilterApplierObj.filterComponent().setData).toHaveBeenCalledWith({ + 'name': 'test', + 'qty': '1' + }, false); + expect(urlFilterApplierObj.filterComponent().apply).toHaveBeenCalled(); + }); + }); + }); +});