From d3d300b33430353946f3373a4d64cac578715010 Mon Sep 17 00:00:00 2001 From: Luke Rodgers Date: Sun, 3 Dec 2017 12:21:58 +0000 Subject: [PATCH] Update indexer:status to display schedule backlog --- .../Console/Command/IndexerStatusCommand.php | 89 +++++++-- .../Command/IndexerStatusCommandTest.php | 177 ++++++++++++++++-- 2 files changed, 234 insertions(+), 32 deletions(-) diff --git a/app/code/Magento/Indexer/Console/Command/IndexerStatusCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerStatusCommand.php index 6590f6e0af99d..2d9c4bd3ccb28 100644 --- a/app/code/Magento/Indexer/Console/Command/IndexerStatusCommand.php +++ b/app/code/Magento/Indexer/Console/Command/IndexerStatusCommand.php @@ -7,6 +7,8 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Magento\Framework\Indexer; +use Magento\Framework\Mview; /** * Command for displaying status of indexers. @@ -30,21 +32,84 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + $table = $this->getHelperSet()->get('table'); + $table->setHeaders(['Title', 'Status', 'Update On', 'Schedule Status', 'Schedule Updated']); + + $rows = []; + $indexers = $this->getIndexers($input); foreach ($indexers as $indexer) { - $status = 'unknown'; - switch ($indexer->getStatus()) { - case \Magento\Framework\Indexer\StateInterface::STATUS_VALID: - $status = 'Ready'; - break; - case \Magento\Framework\Indexer\StateInterface::STATUS_INVALID: - $status = 'Reindex required'; - break; - case \Magento\Framework\Indexer\StateInterface::STATUS_WORKING: - $status = 'Processing'; - break; + $view = $indexer->getView(); + + $rowData = [ + 'Title' => $indexer->getTitle(), + 'Status' => $this->getStatus($indexer), + 'Update On' => $indexer->isScheduled() ? 'Schedule' : 'Save', + 'Schedule Status' => '', + 'Updated' => '', + ]; + + if ($indexer->isScheduled()) { + $state = $view->getState(); + $rowData['Schedule Status'] = "{$state->getStatus()} ({$this->getPendingCount($view)} in backlog)"; + $rowData['Updated'] = $state->getUpdated(); } - $output->writeln(sprintf('%-50s ', $indexer->getTitle() . ':') . $status); + + $rows[] = $rowData; + } + + usort($rows, function ($comp1, $comp2) { + return strcmp($comp1['Title'], $comp2['Title']); + }); + + $table->addRows($rows); + $table->render($output); + } + + /** + * @param Indexer\IndexerInterface $indexer + * @return string + */ + protected function getStatus(Indexer\IndexerInterface $indexer) + { + $status = 'unknown'; + switch ($indexer->getStatus()) { + case \Magento\Framework\Indexer\StateInterface::STATUS_VALID: + $status = 'Ready'; + break; + case \Magento\Framework\Indexer\StateInterface::STATUS_INVALID: + $status = 'Reindex required'; + break; + case \Magento\Framework\Indexer\StateInterface::STATUS_WORKING: + $status = 'Processing'; + break; } + return $status; + } + + /** + * @param Mview\ViewInterface $view + * @return string + */ + protected function getPendingCount(Mview\ViewInterface $view) + { + $changelog = $view->getChangelog(); + + try { + $currentVersionId = $changelog->getVersion(); + } catch (Mview\View\ChangelogTableNotExistsException $e) { + return ''; + } + + $state = $view->getState(); + + $pendingCount = $changelog->getListSize($state->getVersionId(), $currentVersionId); + + $pendingString = "$pendingCount"; + if ($pendingCount <= 0) { + $pendingString = "$pendingCount"; + } + + return $pendingString; } } diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusCommandTest.php index 6eb7f7562b9cc..58a0a5b750709 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusCommandTest.php @@ -8,6 +8,8 @@ use Magento\Framework\Indexer\StateInterface; use Magento\Indexer\Console\Command\IndexerStatusCommand; use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\TableHelper; class IndexerStatusCommandTest extends AbstractIndexerCommandCommonSetup { @@ -18,35 +20,132 @@ class IndexerStatusCommandTest extends AbstractIndexerCommandCommonSetup */ private $command; + /** + * @param \PHPUnit_Framework_MockObject_MockObject $indexerMock + * @param array $data + * @return mixed + */ + protected function attachViewToIndexerMock($indexerMock, array $data) + { + /** @var \Magento\Framework\Mview\View\Changelog|\PHPUnit_Framework_MockObject_MockObject $stub */ + $changelog = $this->getMockBuilder(\Magento\Framework\Mview\View\Changelog::class) + ->disableOriginalConstructor() + ->getMock(); + + $changelog->expects($this->any()) + ->method('getListSize') + ->willReturn($data['view']['changelog']['list_size']); + + /** @var \Magento\Indexer\Model\Mview\View\State|\PHPUnit_Framework_MockObject_MockObject $stateMock */ + $stateMock = $this->getMockBuilder(\Magento\Indexer\Model\Mview\View\State::class) + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + + $stateMock->addData($data['view']['state']); + + /** @var \Magento\Framework\Mview\View|\PHPUnit_Framework_MockObject_MockObject $viewMock */ + $viewMock = $this->getMockBuilder(\Magento\Framework\Mview\View::class) + ->disableOriginalConstructor() + ->setMethods(['getChangelog', 'getState']) + ->getMock(); + + $viewMock->expects($this->any()) + ->method('getState') + ->willReturn($stateMock); + $viewMock->expects($this->any()) + ->method('getChangelog') + ->willReturn($changelog); + + $indexerMock->method('getView') + ->willReturn($viewMock); + + return $indexerMock; + } + /** * @param array $indexers - * @param array $statuses + * * @dataProvider executeAllDataProvider */ - public function testExecuteAll(array $indexers, array $statuses) + public function testExecuteAll(array $indexers) { $this->configureAdminArea(); $indexerMocks = []; foreach ($indexers as $indexerData) { $indexerMock = $this->getIndexerMock( - ['getStatus'], + ['getStatus', 'isScheduled', 'getState', 'getView'], $indexerData ); + $indexerMock->method('getStatus') - ->willReturn($statuses[$indexerData['indexer_id']]); + ->willReturn($indexerData['status']); + $indexerMock->method('isScheduled') + ->willReturn($indexerData['is_scheduled']); + + if ($indexerData['is_scheduled']) { + $this->attachViewToIndexerMock($indexerMock, $indexerData); + } + $indexerMocks[] = $indexerMock; + } $this->initIndexerCollectionByItems($indexerMocks); $this->command = new IndexerStatusCommand($this->objectManagerFactory); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->command->setHelperSet( + $objectManager->getObject( + HelperSet::class, + ['helpers' => [$objectManager->getObject(TableHelper::class)]] + ) + ); + + $commandTester = new CommandTester($this->command); $commandTester->execute([]); - $actualValue = $commandTester->getDisplay(); - $expectedValue = sprintf('%-50s ', 'Title_indexerOne' . ':') . 'Ready' . PHP_EOL - . sprintf('%-50s ', 'Title_indexerTwo' . ':') . 'Reindex required' . PHP_EOL - . sprintf('%-50s ', 'Title_indexerThree' . ':') . 'Processing' . PHP_EOL - . sprintf('%-50s ', 'Title_indexerFour' . ':') . 'unknown' . PHP_EOL; - $this->assertStringStartsWith($expectedValue, $actualValue); + $linesOutput = array_filter(explode(PHP_EOL, $commandTester->getDisplay())); + + $this->assertCount(8, $linesOutput, 'There should be 8 lines output. 3 Spacers, 1 header, 4 content.'); + $this->assertEquals($linesOutput[0], $linesOutput[2], "Lines 0, 2, 7 should be spacer lines"); + $this->assertEquals($linesOutput[2], $linesOutput[7], "Lines 0, 2, 6 should be spacer lines"); + + $headerValues = array_values(array_filter(explode('|', $linesOutput[1]))); + $this->assertEquals('Title', trim($headerValues[0])); + $this->assertEquals('Status', trim($headerValues[1])); + $this->assertEquals('Update On', trim($headerValues[2])); + $this->assertEquals('Schedule Status', trim($headerValues[3])); + $this->assertEquals('Schedule Updated', trim($headerValues[4])); + + $indexer1 = array_values(array_filter(explode('|', $linesOutput[3]))); + $this->assertEquals('Title_indexer1', trim($indexer1[0])); + $this->assertEquals('Ready', trim($indexer1[1])); + $this->assertEquals('Schedule', trim($indexer1[2])); + $this->assertEquals('idle (10 in backlog)', trim($indexer1[3])); + $this->assertEquals('2017-01-01 11:11:11', trim($indexer1[4])); + + $indexer2 = array_values(array_filter(explode('|', $linesOutput[4]))); + $this->assertEquals('Title_indexer2', trim($indexer2[0])); + $this->assertEquals('Reindex required', trim($indexer2[1])); + $this->assertEquals('Save', trim($indexer2[2])); + $this->assertEquals('', trim($indexer2[3])); + $this->assertEquals('', trim($indexer2[4])); + + $indexer3 = array_values(array_filter(explode('|', $linesOutput[5]))); + $this->assertEquals('Title_indexer3', trim($indexer3[0])); + $this->assertEquals('Processing', trim($indexer3[1])); + $this->assertEquals('Schedule', trim($indexer3[2])); + $this->assertEquals('idle (100 in backlog)', trim($indexer3[3])); + $this->assertEquals('2017-01-01 11:11:11', trim($indexer3[4])); + + $indexer4 = array_values(array_filter(explode('|', $linesOutput[6]))); + $this->assertEquals('Title_indexer4', trim($indexer4[0])); + $this->assertEquals('unknown', trim($indexer4[1])); + $this->assertEquals('Schedule', trim($indexer4[2])); + $this->assertEquals('running (20 in backlog)', trim($indexer4[3])); + $this->assertEquals('2017-01-01 11:11:11', trim($indexer4[4])); } /** @@ -59,27 +158,65 @@ public function executeAllDataProvider() 'indexers' => [ 'indexer_1' => [ 'indexer_id' => 'indexer_1', - 'title' => 'Title_indexerOne' + 'title' => 'Title_indexer1', + 'status' => StateInterface::STATUS_VALID, + 'is_scheduled' => true, + 'view' => [ + 'state' => [ + 'status' => 'idle', + 'updated' => '2017-01-01 11:11:11', + ], + 'changelog' => [ + 'list_size' => 10 + ] + ] ], 'indexer_2' => [ 'indexer_id' => 'indexer_2', - 'title' => 'Title_indexerTwo' + 'title' => 'Title_indexer2', + 'status' => StateInterface::STATUS_INVALID, + 'is_scheduled' => false, + 'view' => [ + 'state' => [ + 'status' => 'idle', + 'updated' => '2017-01-01 11:11:11', + ], + 'changelog' => [ + 'list_size' => 99999999 + ] + ] ], 'indexer_3' => [ 'indexer_id' => 'indexer_3', - 'title' => 'Title_indexerThree' + 'title' => 'Title_indexer3', + 'status' => StateInterface::STATUS_WORKING, + 'is_scheduled' => true, + 'view' => [ + 'state' => [ + 'status' => 'idle', + 'updated' => '2017-01-01 11:11:11', + ], + 'changelog' => [ + 'list_size' => 100 + ] + ] ], 'indexer_4' => [ 'indexer_id' => 'indexer_4', - 'title' => 'Title_indexerFour' + 'title' => 'Title_indexer4', + 'status' => null, + 'is_scheduled' => true, + 'view' => [ + 'state' => [ + 'status' => 'running', + 'updated' => '2017-01-01 11:11:11', + ], + 'changelog' => [ + 'list_size' => 20 + ] + ] ], ], - 'Statuses' => [ - 'indexer_1' => StateInterface::STATUS_VALID, - 'indexer_2' => StateInterface::STATUS_INVALID, - 'indexer_3' => StateInterface::STATUS_WORKING, - 'indexer_4' => null, - ] ], ]; }