diff --git a/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/Index.php b/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/Index.php
index 4bae57f80209e..d51fd658075f1 100644
--- a/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/Index.php
+++ b/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/Index.php
@@ -21,6 +21,7 @@ public function execute()
{
$this->_view->loadLayout();
$this->_setActiveMenu('Magento_Theme::system_design_theme');
+ $this->_view->getLayout()->getBlock('page.title')->setPageTitle('Themes');
$this->_view->renderLayout();
}
}
diff --git a/app/code/Magento/Theme/Model/ResourceModel/Theme/Collection.php b/app/code/Magento/Theme/Model/ResourceModel/Theme/Collection.php
index f2f95d24a368b..f9dd501520109 100644
--- a/app/code/Magento/Theme/Model/ResourceModel/Theme/Collection.php
+++ b/app/code/Magento/Theme/Model/ResourceModel/Theme/Collection.php
@@ -17,6 +17,11 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab
*/
const DEFAULT_PAGE_SIZE = 6;
+ /**
+ * @var string
+ */
+ protected $_idFieldName = 'theme_id';
+
/**
* Collection initialization
*
diff --git a/app/code/Magento/Theme/Model/ResourceModel/Theme/Grid/Collection.php b/app/code/Magento/Theme/Model/ResourceModel/Theme/Grid/Collection.php
index f708b3c7889f3..c4a7bb11a78f7 100644
--- a/app/code/Magento/Theme/Model/ResourceModel/Theme/Grid/Collection.php
+++ b/app/code/Magento/Theme/Model/ResourceModel/Theme/Grid/Collection.php
@@ -7,13 +7,15 @@
/**
* Theme grid collection
+ * @deprecated
+ * @see \Magento\Theme\Ui\Component\Theme\DataProvider\SearchResult
*/
class Collection extends \Magento\Theme\Model\ResourceModel\Theme\Collection
{
/**
* Add area filter
*
- * @return \Magento\Theme\Model\ResourceModel\Theme\Collection
+ * @return $this
*/
protected function _initSelect()
{
diff --git a/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/System/Design/Theme/IndexTest.php b/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/System/Design/Theme/IndexTest.php
index a5acb67f93974..213d8dde8689f 100644
--- a/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/System/Design/Theme/IndexTest.php
+++ b/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/System/Design/Theme/IndexTest.php
@@ -28,13 +28,18 @@ public function testIndexAction()
->method('getMenuModel')
->will($this->returnValue($menuModel));
+ $titleBlock = $this->createMock(\Magento\Theme\Block\Html\Title::class);
+ $titleBlock->expects($this->once())->method('setPageTitle');
+
$layout = $this->createMock(\Magento\Framework\View\LayoutInterface::class);
$layout->expects($this->any())
->method('getBlock')
- ->with($this->equalTo('menu'))
- ->will($this->returnValue($menuBlock));
+ ->willReturnMap([
+ ['menu', $menuBlock],
+ ['page.title', $titleBlock]
+ ]);
- $this->view->expects($this->once())
+ $this->view->expects($this->any())
->method('getLayout')
->will($this->returnValue($layout));
diff --git a/app/code/Magento/Theme/Test/Unit/Ui/Component/Listing/Column/ViewActionTest.php b/app/code/Magento/Theme/Test/Unit/Ui/Component/Listing/Column/ViewActionTest.php
new file mode 100644
index 0000000000000..03d1fe70f2f07
--- /dev/null
+++ b/app/code/Magento/Theme/Test/Unit/Ui/Component/Listing/Column/ViewActionTest.php
@@ -0,0 +1,121 @@
+objectManager = new ObjectManager($this);
+ $this->urlBuilder = $this->getMockForAbstractClass(\Magento\Framework\UrlInterface::class);
+ }
+
+ /**
+ * @param array $data
+ * @param array $dataSourceItems
+ * @param array $expectedDataSourceItems
+ * @param string $expectedUrlPath
+ * @param array $expectedUrlParam
+ *
+ * @dataProvider getPrepareDataSourceDataProvider
+ * @return void
+ */
+ public function testPrepareDataSource(
+ $data,
+ $dataSourceItems,
+ $expectedDataSourceItems,
+ $expectedUrlPath,
+ $expectedUrlParam
+ ) {
+ $contextMock = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\ContextInterface::class)
+ ->getMockForAbstractClass();
+ $processor = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\Processor::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $contextMock->expects($this->never())->method('getProcessor')->willReturn($processor);
+ $this->model = $this->objectManager->getObject(
+ ViewAction::class,
+ [
+ 'urlBuilder' => $this->urlBuilder,
+ 'data' => $data,
+ 'context' => $contextMock,
+ ]
+ );
+
+ $this->urlBuilder->expects($this->once())
+ ->method('getUrl')
+ ->with($expectedUrlPath, $expectedUrlParam)
+ ->willReturn('url');
+
+ $dataSource = [
+ 'data' => [
+ 'items' => $dataSourceItems
+ ]
+ ];
+ $dataSource = $this->model->prepareDataSource($dataSource);
+ $this->assertEquals($expectedDataSourceItems, $dataSource['data']['items']);
+ }
+
+ /**
+ * Data provider for testPrepareDataSource
+ * @return array
+ */
+ public function getPrepareDataSourceDataProvider()
+ {
+ return [
+ [
+ ['name' => 'itemName', 'config' => []],
+ [['itemName' => '', 'entity_id' => 1]],
+ [['itemName' => ['view' => ['href' => 'url', 'label' => __('View')]], 'entity_id' => 1]],
+ '#',
+ ['id' => 1]
+ ],
+ [
+ ['name' => 'itemName', 'config' => [
+ 'viewUrlPath' => 'url_path',
+ 'urlEntityParamName' => 'theme_id',
+ 'indexField' => 'theme_id']
+ ],
+ [['itemName' => '', 'theme_id' => 2]],
+ [['itemName' => ['view' => ['href' => 'url', 'label' => __('View')]], 'theme_id' => 2]],
+ 'url_path',
+ ['theme_id' => 2]
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Theme/Ui/Component/Listing/Column/ViewAction.php b/app/code/Magento/Theme/Ui/Component/Listing/Column/ViewAction.php
new file mode 100644
index 0000000000000..774d5bab660af
--- /dev/null
+++ b/app/code/Magento/Theme/Ui/Component/Listing/Column/ViewAction.php
@@ -0,0 +1,77 @@
+urlBuilder = $urlBuilder;
+ parent::__construct($context, $uiComponentFactory, $components, $data);
+ }
+
+ /**
+ * Prepare Theme Data Source
+ *
+ * @param array $dataSource
+ * @return array
+ */
+ public function prepareDataSource(array $dataSource) : array
+ {
+ if (isset($dataSource['data']['items'])) {
+ foreach ($dataSource['data']['items'] as & $item) {
+ $indexField = $this->getData('config/indexField') ?: 'entity_id';
+ if (isset($item[$indexField])) {
+ $viewUrlPath = $this->getData('config/viewUrlPath') ?: '#';
+ $urlEntityParamName = $this->getData('config/urlEntityParamName') ?: 'id';
+ $item[$this->getData('name')] = [
+ 'view' => [
+ 'href' => $this->urlBuilder->getUrl(
+ $viewUrlPath,
+ [
+ $urlEntityParamName => $item[$indexField]
+ ]
+ ),
+ 'label' => __('View')
+ ]
+ ];
+ }
+ }
+ }
+
+ return $dataSource;
+ }
+}
diff --git a/app/code/Magento/Theme/Ui/Component/Theme/DataProvider/SearchResult.php b/app/code/Magento/Theme/Ui/Component/Theme/DataProvider/SearchResult.php
new file mode 100644
index 0000000000000..696b38a71761a
--- /dev/null
+++ b/app/code/Magento/Theme/Ui/Component/Theme/DataProvider/SearchResult.php
@@ -0,0 +1,53 @@
+ [
+ 'theme_id' => 'main_table.theme_id',
+ 'theme_title' => 'main_table.theme_title',
+ 'theme_path' => 'main_table.theme_path',
+ 'parent_theme_title' => 'parent.theme_title',
+ ],
+ ];
+
+ /**
+ * Add area and type filters
+ * Join parent theme title
+ *
+ * @return $this
+ */
+ protected function _initSelect()
+ {
+ parent::_initSelect();
+ $this
+ ->addFieldToFilter('main_table.area', \Magento\Framework\App\Area::AREA_FRONTEND)
+ ->addFieldToFilter('main_table.type', ['in' => [
+ \Magento\Framework\View\Design\ThemeInterface::TYPE_PHYSICAL,
+ \Magento\Framework\View\Design\ThemeInterface::TYPE_VIRTUAL,
+ ]])
+ ;
+
+ $this->getSelect()->joinLeft(
+ ['parent' => $this->getMainTable()],
+ 'main_table.parent_id = parent.theme_id',
+ ['parent_theme_title' => 'parent.theme_title']
+ );
+
+ return $this;
+ }
+}
diff --git a/app/code/Magento/Theme/etc/di.xml b/app/code/Magento/Theme/etc/di.xml
index c20184fec6bc4..55119f3449389 100644
--- a/app/code/Magento/Theme/etc/di.xml
+++ b/app/code/Magento/Theme/etc/di.xml
@@ -108,6 +108,7 @@