diff --git a/app/code/Magento/Indexer/Console/Command/IndexerStatusMviewCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerStatusMviewCommand.php new file mode 100644 index 0000000000000000000000000000000000000000..4fb0c0bcb56493b1e4de1c8744cc1af509719efb --- /dev/null +++ b/app/code/Magento/Indexer/Console/Command/IndexerStatusMviewCommand.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Indexer\Console\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Command\Command; +use Magento\Framework\Mview\View; + +/** + * Command for displaying status of mview indexers. + */ +class IndexerStatusMviewCommand extends Command +{ + /** @var \Magento\Framework\Mview\View\CollectionInterface $mviewIndexersCollection */ + private $mviewIndexersCollection; + + public function __construct( + \Magento\Framework\Mview\View\CollectionInterface $collection + ) { + $this->mviewIndexersCollection = $collection; + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('indexer:status:mview') + ->setDescription('Shows status of Mview Indexers and their queue status'); + + parent::configure(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + try { + $table = $this->getHelperSet()->get('table'); + $table->setHeaders(['ID', 'Mode', 'Status', 'Updated', 'Version ID', 'Backlog']); + + $rows = []; + + /** @var \Magento\Framework\Mview\View $indexer */ + foreach ($this->mviewIndexersCollection as $indexer) { + $state = $indexer->getState(); + $changelog = $indexer->getChangelog(); + + try { + $currentVersionId = $changelog->getVersion(); + } catch (View\ChangelogTableNotExistsException $e) { + continue; + } + + $pendingCount = count($changelog->getList($state->getVersionId(), $currentVersionId)); + + $pendingString = "<error>$pendingCount</error>"; + if ($pendingCount <= 0) { + $pendingString = "<info>$pendingCount</info>"; + } + + $rows[] = [ + $indexer->getData('view_id'), + $state->getData('mode'), + $state->getData('status'), + $state->getData('updated'), + $state->getData('version_id'), + $pendingString, + ]; + } + + usort($rows, function($a, $b) { + return $a[0] <=> $b[0]; + }); + + $table->addRows($rows); + $table->render($output); + + return \Magento\Framework\Console\Cli::RETURN_SUCCESS; + } catch (\Exception $e) { + $output->writeln('<error>' . $e->getMessage() . '</error>'); + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { + $output->writeln($e->getTraceAsString()); + } + + return \Magento\Framework\Console\Cli::RETURN_FAILURE; + } + } +} diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusMviewCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusMviewCommandTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7266d009a5ee7285b1bbf40c38ac71c6fab8722f --- /dev/null +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusMviewCommandTest.php @@ -0,0 +1,233 @@ +<?php +/** + * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Indexer\Test\Unit\Console\Command; + +use \Magento\Framework\Mview; +use Magento\Indexer\Console\Command\IndexerStatusMviewCommand; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\TableHelper; +use Magento\Store\Model\Website; +use Magento\Framework\Console\Cli; + +class IndexerStatusMviewCommandTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var IndexerStatusMviewCommand + */ + private $command; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManager; + + /** + * @var \Magento\Framework\Mview\View\Collection + */ + private $collection; + + protected function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + /** @var \Magento\Framework\Mview\View\Collection $collection */ + $this->collection = $this->objectManager->getObject(Mview\View\Collection::class); + + $reflectedCollection = new \ReflectionObject($this->collection); + $isLoadedProperty = $reflectedCollection->getProperty('_isCollectionLoaded'); + $isLoadedProperty->setAccessible(true); + $isLoadedProperty->setValue($this->collection, true); + + $this->command = $this->objectManager->getObject( + IndexerStatusMviewCommand::class, + ['collection' => $this->collection] + ); + + /** @var HelperSet $helperSet */ + $helperSet = $this->objectManager->getObject( + HelperSet::class, + ['helpers' => [$this->objectManager->getObject(TableHelper::class)]] + ); + + //Inject table helper for output + $this->command->setHelperSet($helperSet); + } + + public function testExecute() + { + $mviews = [ + [ + 'view' => [ + 'view_id' => 'catalog_category_product', + 'mode' => 'enabled', + 'status' => 'idle', + 'updated' => '2017-01-01 11:11:11', + 'version_id' => 100, + ], + 'changelog' => [ + 'version_id' => 110 + ], + ], + [ + 'view' => [ + 'view_id' => 'catalog_product_category', + 'mode' => 'disabled', + 'status' => 'idle', + 'updated' => '2017-01-01 11:11:11', + 'version_id' => 100, + ], + 'changelog' => [ + 'version_id' => 200 + ], + ], + [ + 'view' => [ + 'view_id' => 'catalog_product_attribute', + 'mode' => 'enabled', + 'status' => 'idle', + 'updated' => '2017-01-01 11:11:11', + 'version_id' => 100, + ], + 'changelog' => [ + 'version_id' => 100 + ], + ], + ]; + + foreach ($mviews as $data) { + $this->collection->addItem($this->generateMviewStub($data['view'], $data['changelog'])); + } + + /** @var Mview\View\Changelog|\PHPUnit_Framework_MockObject_MockObject $stub */ + $changelog = $this->getMockBuilder(\Magento\Framework\Mview\View\Changelog::class) + ->disableOriginalConstructor() + ->getMock(); + + $changelog->expects($this->any()) + ->method('getVersion') + ->willThrowException( + new Mview\View\ChangelogTableNotExistsException(new \Magento\Framework\Phrase("Do not render")) + ); + + /** @var Mview\View|\PHPUnit_Framework_MockObject_MockObject $notInitiatedMview */ + $notInitiatedMview = $this->getMockBuilder(\Magento\Framework\Mview\View::class) + ->disableOriginalConstructor() + ->getMock(); + + $notInitiatedMview->expects($this->any()) + ->method('getChangelog') + ->willReturn($changelog); + + $this->collection->addItem($notInitiatedMview); + + $tester = new CommandTester($this->command); + $this->assertEquals(Cli::RETURN_SUCCESS, $tester->execute([])); + + $linesOutput = array_filter(explode(PHP_EOL, $tester->getDisplay())); + $this->assertCount(7, $linesOutput, 'There should be 7 lines output. 3 Spacers, 1 header, 3 content.'); + $this->assertEquals($linesOutput[0], $linesOutput[2], "Lines 0, 2, 7 should be spacer lines"); + $this->assertEquals($linesOutput[2], $linesOutput[6], "Lines 0, 2, 6 should be spacer lines"); + + $headerValues = array_values(array_filter(explode('|', $linesOutput[1]))); + $this->assertEquals('ID', trim($headerValues[0])); + $this->assertEquals('Mode', trim($headerValues[1])); + $this->assertEquals('Status', trim($headerValues[2])); + $this->assertEquals('Updated', trim($headerValues[3])); + $this->assertEquals('Version ID', trim($headerValues[4])); + $this->assertEquals('Backlog', trim($headerValues[5])); + + $catalogCategoryProductMviewData = array_values(array_filter(explode('|', $linesOutput[3]))); + $this->assertEquals('catalog_category_product', trim($catalogCategoryProductMviewData[0])); + $this->assertEquals('enabled', trim($catalogCategoryProductMviewData[1])); + $this->assertEquals('idle', trim($catalogCategoryProductMviewData[2])); + $this->assertEquals('2017-01-01 11:11:11', trim($catalogCategoryProductMviewData[3])); + $this->assertEquals('100', trim($catalogCategoryProductMviewData[4])); + $this->assertEquals('10', trim($catalogCategoryProductMviewData[5])); + unset($catalogCategoryProductMviewData); + + $catalogProductAttributeMviewData = array_values(array_filter(explode('|', $linesOutput[4]))); + $this->assertEquals('catalog_product_attribute', trim($catalogProductAttributeMviewData[0])); + $this->assertEquals('enabled', trim($catalogProductAttributeMviewData[1])); + $this->assertEquals('idle', trim($catalogProductAttributeMviewData[2])); + $this->assertEquals('2017-01-01 11:11:11', trim($catalogProductAttributeMviewData[3])); + $this->assertEquals('100', trim($catalogProductAttributeMviewData[4])); + $this->assertEquals('0', trim($catalogProductAttributeMviewData[5])); + unset($catalogProductAttributeMviewData); + + $catalogCategoryProductMviewData = array_values(array_filter(explode('|', $linesOutput[5]))); + $this->assertEquals('catalog_product_category', trim($catalogCategoryProductMviewData[0])); + $this->assertEquals('disabled', trim($catalogCategoryProductMviewData[1])); + $this->assertEquals('idle', trim($catalogCategoryProductMviewData[2])); + $this->assertEquals('2017-01-01 11:11:11', trim($catalogCategoryProductMviewData[3])); + $this->assertEquals('100', trim($catalogCategoryProductMviewData[4])); + $this->assertEquals('100', trim($catalogCategoryProductMviewData[5])); + unset($catalogCategoryProductMviewData); + } + + /** + * @param array $viewData + * @param array $changelogData + * @return Mview\View|Mview\View\Changelog|\PHPUnit_Framework_MockObject_MockObject + */ + protected function generateMviewStub(array $viewData, array $changelogData) + { + /** @var Mview\View\Changelog|\PHPUnit_Framework_MockObject_MockObject $stub */ + $changelog = $this->getMockBuilder(\Magento\Framework\Mview\View\Changelog::class) + ->disableOriginalConstructor() + ->getMock(); + + $list = []; + if ($changelogData['version_id'] !== $viewData['version_id']) { + $list = range($viewData['version_id']+1, $changelogData['version_id']); + } + + $changelog->expects($this->any()) + ->method('getList') + ->willReturn($list); + + $changelog->expects($this->any()) + ->method('getVersion') + ->willReturn($changelogData['version_id']); + + /** @var Mview\View|\PHPUnit_Framework_MockObject_MockObject $stub */ + $stub = $this->getMockBuilder(\Magento\Framework\Mview\View::class) + ->disableOriginalConstructor() + ->setMethods(['getChangelog', 'getState']) + ->getMock(); + + $stub->expects($this->any()) + ->method('getChangelog') + ->willReturn($changelog); + + $stub->expects($this->any()) + ->method('getState') + ->willReturnSelf(); + + $stub->setData($viewData); + + return $stub; + } + + public function testExecuteExceptionNoVerbosity() + { + /** @var \Magento\Framework\Mview\View|\PHPUnit_Framework_MockObject_MockObject $stub */ + $stub = $this->getMockBuilder(Mview\View::class) + ->disableOriginalConstructor() + ->getMock(); + + $stub->expects($this->any()) + ->method('getChangelog') + ->willThrowException(new \Exception("Dummy test exception")); + + $this->collection->addItem($stub); + + $tester = new CommandTester($this->command); + $this->assertEquals(Cli::RETURN_FAILURE, $tester->execute([])); + $linesOutput = array_filter(explode(PHP_EOL, $tester->getDisplay())); + $this->assertEquals('Dummy test exception', $linesOutput[0]); + } +} diff --git a/app/code/Magento/Indexer/etc/di.xml b/app/code/Magento/Indexer/etc/di.xml index 610f08fac3a05cf5cefa128ec769571fa5d00691..266cf72c50dbfc2afe3aa2bb44deec904b206000 100644 --- a/app/code/Magento/Indexer/etc/di.xml +++ b/app/code/Magento/Indexer/etc/di.xml @@ -51,6 +51,7 @@ <item name="set-mode" xsi:type="object">Magento\Indexer\Console\Command\IndexerSetModeCommand</item> <item name="show-mode" xsi:type="object">Magento\Indexer\Console\Command\IndexerShowModeCommand</item> <item name="status" xsi:type="object">Magento\Indexer\Console\Command\IndexerStatusCommand</item> + <item name="status-mview" xsi:type="object">Magento\Indexer\Console\Command\IndexerStatusMviewCommand</item> <item name="reset" xsi:type="object">Magento\Indexer\Console\Command\IndexerResetStateCommand</item> </argument> </arguments>