From 26339b128a9815f13c27a2042bd59c42c0a58d29 Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Tue, 2 Sep 2014 13:16:21 +0200 Subject: [PATCH 1/2] Add disabled property and implement the functionality refs #6986 --- .../Icinga/Web/Widget/Dashboard/Component.php | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/library/Icinga/Web/Widget/Dashboard/Component.php b/library/Icinga/Web/Widget/Dashboard/Component.php index 87f756282..7410c6210 100644 --- a/library/Icinga/Web/Widget/Dashboard/Component.php +++ b/library/Icinga/Web/Widget/Dashboard/Component.php @@ -42,6 +42,13 @@ class Component extends AbstractWidget */ private $pane; + /** + * The disabled option is used to "delete" default dashlets provided by modules + * + * @var bool + */ + private $disabled = false; + /** * The template string used for rendering this widget * @@ -117,6 +124,26 @@ EOD; return $this; } + /** + * Set the disabled property + * + * @param boolean $disabled + */ + public function setDisabled($disabled) + { + $this->disabled = $disabled; + } + + /** + * Get the disabled property + * + * @return boolean + */ + public function getDisabled() + { + return $this->disabled; + } + /** * Return this component's structure as array * @@ -136,6 +163,10 @@ EOD; */ public function render() { + if ($this->disabled === true) { + return ''; + } + $view = $this->view(); $url = clone($this->url); $url->setParam('view', 'compact'); From 7cfc051228588f1635f478184274c9d436929a1d Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Wed, 3 Sep 2014 14:36:04 +0200 Subject: [PATCH 2/2] Add tests for Dashboard and fix doc blocks refs #6986 --- library/Icinga/Web/Widget/Dashboard.php | 47 +- library/Icinga/Web/Widget/Dashboard/Pane.php | 12 +- .../library/Icinga/Widget/DashboardTest.php | 509 ++++++++++++++++++ 3 files changed, 546 insertions(+), 22 deletions(-) create mode 100644 test/php/library/Icinga/Widget/DashboardTest.php diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php index d8b09b15a..a86204d87 100644 --- a/library/Icinga/Web/Widget/Dashboard.php +++ b/library/Icinga/Web/Widget/Dashboard.php @@ -8,7 +8,6 @@ use Icinga\Application\Icinga; use Icinga\Application\Config as IcingaConfig; use Icinga\Exception\ConfigurationError; use Icinga\Exception\ProgrammingError; -use Icinga\Web\Widget\AbstractWidget; use Icinga\Web\Widget\Dashboard\Pane; use Icinga\Web\Widget\Dashboard\Component as DashboardComponent; use Icinga\Web\Url; @@ -96,7 +95,7 @@ class Dashboard extends AbstractWidget $current = $this->panes[$pane->getName()]; $current->addComponents($pane->getComponents()); } else { - $this->panes = array_filter(array_merge($this->panes, $panes)); + $this->panes[$pane->getName()] = $pane; } } @@ -128,6 +127,16 @@ class Dashboard extends AbstractWidget return $this->tabs; } + /** + * Return all panes of this dashboard + * + * @return array + */ + public function getPanes() + { + return $this->panes; + } + /** * Populate this dashboard via the given configuration file * @@ -164,9 +173,9 @@ class Dashboard extends AbstractWidget * * @TODO: Should only allow component objects to be added directly as soon as we store more information * - * @param string $pane The pane to add the component to - * @param Component|string $component The component to add or the title of the newly created component - * @param $url The url to use for the component + * @param string $pane The pane to add the component to + * @param Component|string $component The component to add or the title of the newly created component + * @param string|null $url The url to use for the component * * @return self */ @@ -198,20 +207,14 @@ class Dashboard extends AbstractWidget } /** - * Return true if a pane doesn't exist or doesn't have any components in it - * - * @param string $pane The name of the pane to check for emptyness + * Check if this dashboard has a specific pane * + * @param $pane string The name of the pane * @return bool */ - public function isEmptyPane($pane) + public function hasPane($pane) { - $paneObj = $this->getPane($pane); - if ($paneObj === null) { - return true; - } - $cmps = $paneObj->getComponents(); - return !empty($cmps); + return array_key_exists($pane, $this->panes); } /** @@ -305,11 +308,11 @@ class Dashboard extends AbstractWidget return $active; } + /** + * @see determineActivePane() + */ public function getActivePane() { - if ($active = $this->getTabs()->getActiveName()) { - return $this->getPane($active); - } return $this->determineActivePane(); } @@ -323,10 +326,12 @@ class Dashboard extends AbstractWidget $active = $this->getTabs()->getActiveName(); if (! $active) { if ($active = Url::fromRequest()->getParam($this->tabParam)) { - if ($this->isEmptyPane($active)) { - $active = $this->setDefaultPane(); - } else { + if ($this->hasPane($active)) { $this->activate($active); + } else { + throw new ProgrammingError( + 'Try to get an inexistent pane.' + ); } } else { $active = $this->setDefaultPane(); diff --git a/library/Icinga/Web/Widget/Dashboard/Pane.php b/library/Icinga/Web/Widget/Dashboard/Pane.php index 39ad1fda3..45bd9c558 100644 --- a/library/Icinga/Web/Widget/Dashboard/Pane.php +++ b/library/Icinga/Web/Widget/Dashboard/Pane.php @@ -39,7 +39,7 @@ class Pane extends AbstractWidget /** * Create a new pane * - * @param $name The pane to create + * @param string $name The pane to create */ public function __construct($name) { @@ -92,6 +92,16 @@ class Pane extends AbstractWidget return array_key_exists($title, $this->components); } + /** + * Checks if the current pane has any components + * + * @return bool + */ + public function hasComponents() + { + return ! empty($this->components); + } + /** * Return a component with the given name if existing * diff --git a/test/php/library/Icinga/Widget/DashboardTest.php b/test/php/library/Icinga/Widget/DashboardTest.php new file mode 100644 index 000000000..cdb4d8ff2 --- /dev/null +++ b/test/php/library/Icinga/Widget/DashboardTest.php @@ -0,0 +1,509 @@ +shouldReceive('escape'); + + return $mock; + } +} + +class DashboardWithPredefinableActiveName extends Dashboard +{ + public $activeName = ''; + + public function getTabs() + { + return Mockery::mock('Icinga\Web\Widget\Tabs') + ->shouldReceive('getActiveName')->andReturn($this->activeName) + ->shouldReceive('activate') + ->getMock(); + } +} + +class DashboardTest extends BaseTestCase +{ + public function tearDown() + { + parent::tearDown(); + Mockery::close(); // Necessary because some tests run in a separate process + } + + protected function setupIcingaMock(\Zend_Controller_Request_Abstract $request) + { + $moduleMock = Mockery::mock('Icinga\Application\Modules\Module'); + $moduleMock->shouldReceive('getPaneItems')->andReturn(array( + 'test-pane' => new Pane('Test Pane') + )); + + $moduleManagerMock = Mockery::mock('Icinga\Application\Modules\Manager'); + $moduleManagerMock->shouldReceive('getLoadedModules')->andReturn(array( + 'test-module' => $moduleMock + )); + + $bootstrapMock = Mockery::mock('Icinga\Application\ApplicationBootstrap')->shouldDeferMissing(); + $bootstrapMock->shouldReceive('getFrontController->getRequest')->andReturnUsing( + function () use ($request) { return $request; } + )->shouldReceive('getApplicationDir')->andReturn(self::$appDir); + + $bootstrapMock->shouldReceive('getModuleManager')->andReturn($moduleManagerMock); + + Icinga::setApp($bootstrapMock, true); + } + + public function testWhetherCreatePaneCreatesAPane() + { + $dashboard = new Dashboard(); + $pane = $dashboard->createPane('test')->getPane('test'); + + $this->assertEquals('test', $pane->getTitle(), 'Dashboard::createPane() could not create a pane'); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testMergePanesWithDifferentPaneName() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $panes = array( + new Pane('test1a'), + new Pane('test2a') + ); + + $dashboard->mergePanes($panes); + + $this->assertCount(4, $dashboard->getPanes(), 'Dashboard::mergePanes() could not merge different panes'); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testMergePanesWithSamePaneName() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $panes = array( + new Pane('test1'), + new Pane('test3') + ); + + $dashboard->mergePanes($panes); + + $this->assertCount(3, $dashboard->getPanes(), 'Dashboard::mergePanes() could not merge same panes'); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherGetPaneReturnsAPaneByName() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + + $pane = $dashboard->getPane('test1'); + + $this->assertEquals( + 'test1', + $pane->getName(), + 'Dashboard:getPane() could not return pane by name' + ); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testLoadPaneItemsProvidedByEnabledModules() + { + $dashboard = Dashboard::load(); + + $this->assertCount( + 1, + $dashboard->getPanes(), + 'Dashboard::load() could not load panes from enabled modules' + ); + } + + /** + * @expectedException \Icinga\Exception\ProgrammingError + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherGetPaneThrowsAnExceptionOnNotExistentPaneName() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + + $dashboard->getPane('test2'); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherRenderNotRendersPanesDisabledComponent() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new ComponentWithMockedView('test', 'test', $pane); + $component->setDisabled(true); + $pane->addComponent($component); + + $rendered = $dashboard->render(); + + $greaterThanOne = strlen($rendered) > 1; + + $this->assertFalse( + $greaterThanOne, + 'Dashboard::render() disabled component is rendered, but should not' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherRenderRendersPanesEnabledComponent() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new ComponentWithMockedView('test', 'test', $pane); + $pane->addComponent($component); + + $rendered = $dashboard->render(); + + $greaterThanOne = strlen($rendered) > 1; + + $this->assertTrue( + $greaterThanOne, + 'Dashboard::render() could not render enabled component' + ); + } + + public function testWhetherRenderNotRendersNotExistentPane() + { + $dashboard = new Dashboard(); + + $rendered = $dashboard->render(); + + $greaterThanOne = strlen($rendered) > 1; + + $this->assertFalse( + $greaterThanOne, + 'Dashboard::render() not existent pane ist rendered, but should not' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherGetPaneKeyTitleArrayReturnFormedArray() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1')->getPane('test1')->setTitle('Test1'); + $dashboard->createPane('test2')->getPane('test2')->setTitle('Test2'); + + $result = $dashboard->getPaneKeyTitleArray(); + + $expected = array( + 'test1' => 'Test1', + 'test2' => 'Test2' + ); + + $this->assertEquals( + $expected, + $result, + 'Dashboard::getPaneKeyTitleArray() could not return valid expectation' + ); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherHasPanesHasPanes() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $hasPanes = $dashboard->hasPanes(); + + $this->assertTrue($hasPanes, 'Dashboard::hasPanes() could not return valid expectation'); + } + + public function testWhetherHasPanesHasNoPanes() + { + $dashboard = new Dashboard(); + + $hasPanes = $dashboard->hasPanes(); + + $this->assertFalse($hasPanes, 'Dashboard::hasPanes() has panes but should not'); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherRemoveComponentRemovesComponent() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $component2 = new Component('test2', 'test2', $pane); + $pane->addComponent($component2); + + $dashboard->removeComponent('test1', 'test'); + + $result = $dashboard->getPane('test1')->hasComponent('test'); + + $this->assertFalse( + $result, + 'Dashboard::removeComponent() could not remove component from the pane' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherRemoveComponentRemovesComponentByConcatenation() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $component2 = new Component('test2', 'test2', $pane); + $pane->addComponent($component2); + + $dashboard->removeComponent('test1.test', null); + + $result = $dashboard->getPane('test1')->hasComponent('test'); + + $this->assertFalse( + $result, + 'Dashboard::removeComponent() could not remove component from the pane' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherToArrayReturnsDashboardStructureAsArray() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $result = $dashboard->toArray(); + + $expected = array( + 'test1' => array( + 'title' => 'test1' + ), + 'test1.test' => array( + 'url' => 'test' + ) + ); + + $this->assertEquals( + $expected, + $result, + 'Dashboard::toArray() could not return valid expectation' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherSetComponentUrlUpdatesTheComponentUrl() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $dashboard->setComponentUrl('test1', 'test', 'new'); + + $this->assertEquals( + 'new', + $component->getUrl()->getPath(), + 'Dashboard::setComponentUrl() could not return valid expectation' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherSetComponentUrlUpdatesTheComponentUrlConcatenation() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $dashboard->setComponentUrl('test1.test', null, 'new'); + + $this->assertEquals( + 'new', + $component->getUrl()->getPath(), + 'Dashboard::setComponentUrl() could not return valid expectation' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherSetComponentUrlUpdatesTheComponentUrlNotExistentPane() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $dashboard->setComponentUrl('test3.test', null, 'new'); + + $result = $dashboard->getPane('test3')->getComponent('test'); + + $this->assertEquals( + 'new', + $result->getUrl()->getPath(), + 'Dashboard::setComponentUrl() could not return valid expectation' + ); + } + + /** + * @expectedException \Icinga\Exception\ConfigurationError + */ + public function testWhetherDetermineActivePaneThrowsAnExceptionIfCouldNotDetermine() + { + $dashboard = new Dashboard(); + $dashboard->determineActivePane(); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @expectedException \Icinga\Exception\ProgrammingError + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherDetermineActivePaneThrowsAnExceptionIfCouldNotDetermineInvalidPane() + { + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->createPane('test1'); + + Mockery::mock('alias:Icinga\Web\Url') + ->shouldReceive('fromRequest->getParam')->andReturn('test2'); + + $dashboard->determineActivePane(); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherDetermineActivePaneDeterminesActivePane() + { + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->activeName = 'test2'; + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $activePane = $dashboard->determineActivePane(); + + $this->assertEquals( + 'test2', + $activePane->getTitle(), + 'Dashboard::determineActivePane() could not determine active pane' + ); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherDetermineActivePaneDeterminesActiveValidPane() + { + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + Mockery::mock('alias:Icinga\Web\Url') + ->shouldReceive('fromRequest->getParam')->andReturn('test2'); + + $activePane = $dashboard->determineActivePane(); + + $this->assertEquals( + 'test2', + $activePane->getTitle(), + 'Dashboard::determineActivePane() could not determine active pane' + ); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherGetActivePaneReturnsActivePane() + { + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->activeName = 'test2'; + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $activePane = $dashboard->getActivePane(); + + $this->assertEquals( + 'test2', + $activePane->getTitle(), + 'Dashboard::determineActivePane() could not get expected active pane' + ); + } + + public function testWhetherLoadConfigPanes() + { + $this->markTestIncomplete( + 'Dashboard::loadConfigPanes() is not fully implemented yet or rather not used' + ); + } + + /** + * @depends testWhetherLoadConfigPanes + */ + public function testWhetherReadConfigPopulatesDashboard() + { + $this->markTestIncomplete( + 'Dashboard::readConfig() is not fully implemented yet or rather not used' + ); + } +}