diff --git a/app/code/Magento/Indexer/Console/Command/IndexerStatusCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerStatusCommand.php index 1e57676b89ba8..4ea640093b67c 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 + */ + private 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 + */ + private function getPendingCount(Mview\ViewInterface $view) + { + $changelog = $view->getChangelog(); + + try { + $currentVersionId = $changelog->getVersion(); + } catch (Mview\View\ChangelogTableNotExistsException $e) { + return ''; + } + + $state = $view->getState(); + + $pendingCount = count($changelog->getList($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 5bedfb2f0223c..55d62429cd28c 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerStatusCommandTest.php @@ -5,9 +5,17 @@ */ namespace Magento\Indexer\Test\Unit\Console\Command; +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; +use Magento\Framework\Indexer\IndexerInterface; +use Magento\Indexer\Model\Indexer\Collection; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class IndexerStatusCommandTest extends AbstractIndexerCommandCommonSetup { /** @@ -17,46 +25,256 @@ class IndexerStatusCommandTest extends AbstractIndexerCommandCommonSetup */ private $command; - public function testExecuteAll() + /** + * @var Collection|\PHPUnit_Framework_MockObject_MockObject + */ + protected $indexerCollection; + + public function setUp() + { + parent::setUp(); + + $this->indexerCollection = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->collectionFactory + ->method('create') + ->willReturn($this->indexerCollection); + } + + /** + * @param \PHPUnit_Framework_MockObject_MockObject $indexerMock + * @param array $data + * @return mixed + */ + private function attachViewToIndexerMock($indexerMock, array $data) + { + /** @var \Magento\Framework\Mview\View\Changelog|\PHPUnit_Framework_MockObject_MockObject $changelog */ + $changelog = $this->getMockBuilder(\Magento\Framework\Mview\View\Changelog::class) + ->disableOriginalConstructor() + ->getMock(); + + $changelog->expects($this->any()) + ->method('getList') + ->willReturn(range(0, $data['view']['changelog']['list_size']-1)); + + /** @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 + * + * @dataProvider executeAllDataProvider + */ + public function testExecuteAll(array $indexers) { $this->configureAdminArea(); - $collection = $this->getMock('Magento\Indexer\Model\Indexer\Collection', [], [], '', false); - $indexerOne = $this->getMock('Magento\Indexer\Model\Indexer', [], [], '', false); - $indexerOne->expects($this->once())->method('getTitle')->willReturn('Title_indexerOne'); - $indexerOne - ->expects($this->once()) - ->method('getStatus') - ->willReturn(\Magento\Framework\Indexer\StateInterface::STATUS_VALID); - $indexerTwo = $this->getMock('Magento\Indexer\Model\Indexer', [], [], '', false); - $indexerTwo->expects($this->once())->method('getTitle')->willReturn('Title_indexerTwo'); - $indexerTwo - ->expects($this->once()) - ->method('getStatus') - ->willReturn(\Magento\Framework\Indexer\StateInterface::STATUS_INVALID); - $indexerThree = $this->getMock('Magento\Indexer\Model\Indexer', [], [], '', false); - $indexerThree->expects($this->once())->method('getTitle')->willReturn('Title_indexerThree'); - $indexerThree - ->expects($this->once()) - ->method('getStatus') - ->willReturn(\Magento\Framework\Indexer\StateInterface::STATUS_WORKING); - $indexerFour = $this->getMock('Magento\Indexer\Model\Indexer', [], [], '', false); - $indexerFour->expects($this->once())->method('getTitle')->willReturn('Title_indexerFour'); - $collection - ->expects($this->once()) - ->method('getItems') - ->willReturn([$indexerOne, $indexerTwo, $indexerThree, $indexerFour]); + $indexerMocks = []; + foreach ($indexers as $indexerData) { + $indexerMock = $this->getIndexerMock( + ['getStatus', 'isScheduled', 'getState', 'getView'], + $indexerData + ); + + $indexerMock->method('getStatus') + ->willReturn($indexerData['status']); + $indexerMock->method('isScheduled') + ->willReturn($indexerData['is_scheduled']); + + if ($indexerData['is_scheduled']) { + $this->attachViewToIndexerMock($indexerMock, $indexerData); + } - $this->collectionFactory->expects($this->once())->method('create')->will($this->returnValue($collection)); - $this->indexerFactory->expects($this->never())->method('create'); + $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())); + + $spacer = '+----------------+------------------+-----------+-------------------------+---------------------+'; + + $this->assertCount(8, $linesOutput, 'There should be 8 lines output. 3 Spacers, 1 header, 4 content.'); + $this->assertEquals($linesOutput[0], $spacer, "Lines 0, 2, 7 should be spacer lines"); + $this->assertEquals($linesOutput[2], $spacer, "Lines 0, 2, 7 should be spacer lines"); + $this->assertEquals($linesOutput[7], $spacer, "Lines 0, 2, 7 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])); + } + + /** + * @return array + */ + public function executeAllDataProvider() + { + return [ + [ + 'indexers' => [ + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + '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_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_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_indexer4', + 'status' => null, + 'is_scheduled' => true, + 'view' => [ + 'state' => [ + 'status' => 'running', + 'updated' => '2017-01-01 11:11:11', + ], + 'changelog' => [ + 'list_size' => 20 + ] + ] + ], + ], + ], + ]; + } + + /** + * @param array $methods + * @param array $data + * @return \PHPUnit_Framework_MockObject_MockObject|IndexerInterface + */ + protected function getIndexerMock(array $methods = [], array $data = []) + { + /** @var \PHPUnit_Framework_MockObject_MockObject|IndexerInterface $indexer */ + $indexer = $this->getMockBuilder(IndexerInterface::class) + ->setMethods(array_merge($methods, ['getId', 'getTitle'])) + ->getMockForAbstractClass(); + $indexer->method('getId') + ->willReturn(isset($data['indexer_id']) ? $data['indexer_id'] : ''); + $indexer->method('getTitle') + ->willReturn(isset($data['title']) ? $data['title'] : ''); + return $indexer; + } + + /** + * Init Indexer Collection Mock by items. + * + * @param IndexerInterface[] $items + * @throws \Exception + */ + protected function initIndexerCollectionByItems(array $items) + { + $this->indexerCollection + ->method('getItems') + ->with() + ->willReturn($items); } } diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js index 67bc04fb46c1f..17e38222e2c31 100644 --- a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js +++ b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js @@ -175,10 +175,10 @@ define([ */ clearEvents: function () { this.fotoramaItem.off( - 'fotorama:show ' + - 'fotorama:showend ' + - 'fotorama:fullscreenenter ' + - 'fotorama:fullscreenexit' + 'fotorama:show.' + this.PV + + ' fotorama:showend.' + this.PV + + ' fotorama:fullscreenenter.' + this.PV + + ' fotorama:fullscreenexit.' + this.PV ); }, @@ -207,7 +207,7 @@ define([ if (options.dataMergeStrategy === 'prepend') { this.options.videoData = [].concat( this.options.optionsVideoData[options.selectedOption], - this.options.videoData + this.defaultVideoData ); } else { this.options.videoData = this.options.optionsVideoData[options.selectedOption]; @@ -232,11 +232,11 @@ define([ * @private */ _listenForFullscreen: function () { - this.fotoramaItem.on('fotorama:fullscreenenter', $.proxy(function () { + this.fotoramaItem.on('fotorama:fullscreenenter.' + this.PV, $.proxy(function () { this.isFullscreen = true; }, this)); - this.fotoramaItem.on('fotorama:fullscreenexit', $.proxy(function () { + this.fotoramaItem.on('fotorama:fullscreenexit.' + this.PV, $.proxy(function () { this.isFullscreen = false; this._hideVideoArrows(); }, this)); @@ -468,7 +468,7 @@ define([ t; if (!fotorama.activeFrame.$navThumbFrame) { - this.fotoramaItem.on('fotorama:showend', $.proxy(function (evt, fotoramaData) { + this.fotoramaItem.on('fotorama:showend.' + this.PV, $.proxy(function (evt, fotoramaData) { $(fotoramaData.activeFrame.$stageFrame).removeAttr('href'); }, this)); @@ -486,7 +486,7 @@ define([ this._checkForVideo(e, fotorama, t + 1); } - this.fotoramaItem.on('fotorama:showend', $.proxy(function (evt, fotoramaData) { + this.fotoramaItem.on('fotorama:showend.' + this.PV, $.proxy(function (evt, fotoramaData) { $(fotoramaData.activeFrame.$stageFrame).removeAttr('href'); }, this)); }, @@ -528,15 +528,15 @@ define([ * @private */ _attachFotoramaEvents: function () { - this.fotoramaItem.on('fotorama:showend', $.proxy(function (e, fotorama) { + this.fotoramaItem.on('fotorama:showend.' + this.PV, $.proxy(function (e, fotorama) { this._startPrepareForPlayer(e, fotorama); }, this)); - this.fotoramaItem.on('fotorama:show', $.proxy(function (e, fotorama) { + this.fotoramaItem.on('fotorama:show.' + this.PV, $.proxy(function (e, fotorama) { this._unloadVideoPlayer(fotorama.activeFrame.$stageFrame.parent(), fotorama, true); }, this)); - this.fotoramaItem.on('fotorama:fullscreenexit', $.proxy(function (e, fotorama) { + this.fotoramaItem.on('fotorama:fullscreenexit.' + this.PV, $.proxy(function (e, fotorama) { fotorama.activeFrame.$stageFrame.find('.' + this.PV).remove(); this._startPrepareForPlayer(e, fotorama); }, this)); diff --git a/app/code/Magento/Webapi/Controller/Rest.php b/app/code/Magento/Webapi/Controller/Rest.php index c1b84a91d97ae..6db02a0b770e7 100644 --- a/app/code/Magento/Webapi/Controller/Rest.php +++ b/app/code/Magento/Webapi/Controller/Rest.php @@ -280,7 +280,7 @@ protected function processSchemaRequest() $responseBody = $this->swaggerGenerator->generate( $requestedServices, $this->_request->getScheme(), - $this->_request->getHttpHost(), + $this->_request->getHttpHost(false), $this->_request->getRequestUri() ); $this->_response->setBody($responseBody)->setHeader('Content-Type', 'application/json');