diff --git a/PHPCI/Controller/BuildStatusController.php b/PHPCI/Controller/BuildStatusController.php index 06f9400b..62442bf8 100644 --- a/PHPCI/Controller/BuildStatusController.php +++ b/PHPCI/Controller/BuildStatusController.php @@ -15,6 +15,7 @@ use b8\Store; use PHPCI\BuildFactory; use PHPCI\Model\Project; use PHPCI\Model\Build; +use PHPCI\Service\BuildStatusService; /** * Build Status Controller - Allows external access to build status information / images. @@ -24,10 +25,9 @@ use PHPCI\Model\Build; */ class BuildStatusController extends \PHPCI\Controller { - /** - * @var \PHPCI\Store\ProjectStore - */ + /* @var \PHPCI\Store\ProjectStore */ protected $projectStore; + /* @var \PHPCI\Store\BuildStore */ protected $buildStore; /** @@ -70,6 +70,62 @@ class BuildStatusController extends \PHPCI\Controller return $status; } + /** + * Displays projects information in ccmenu format + * + * @param $projectId + * @return bool + * @throws \Exception + * @throws b8\Exception\HttpException + */ + public function ccxml($projectId) + { + /* @var Project $project */ + $project = $this->projectStore->getById($projectId); + $xml = new \SimpleXMLElement(''); + + if (!$project instanceof Project || !$project->getAllowPublicStatus()) { + return $this->renderXml($xml); + } + + try { + $branchList = $this->buildStore->getBuildBranches($projectId); + + if (!$branchList) { + $branchList = array($project->getBranch()); + } + + foreach ($branchList as $branch) { + $buildStatusService = new BuildStatusService($branch, $project, $project->getLatestBuild($branch)); + if ($attributes = $buildStatusService->toArray()) { + $projectXml = $xml->addChild('Project'); + foreach ($attributes as $attributeKey => $attributeValue) { + $projectXml->addAttribute($attributeKey, $attributeValue); + } + } + } + + } catch (\Exception $e) { + $xml = new \SimpleXMLElement(''); + } + + return $this->renderXml($xml); + } + + /** + * @param \SimpleXMLElement $xml + * @return bool + */ + protected function renderXml(\SimpleXMLElement $xml = null) + { + $this->response->setHeader('Content-Type', 'text/xml'); + $this->response->setContent($xml->asXML()); + $this->response->flush(); + echo $xml->asXML(); + + return true; + } + /** * Returns the appropriate build status image in SVG format for a given project. */ diff --git a/PHPCI/Service/BuildStatusService.php b/PHPCI/Service/BuildStatusService.php new file mode 100644 index 00000000..b4f3c009 --- /dev/null +++ b/PHPCI/Service/BuildStatusService.php @@ -0,0 +1,222 @@ +project = $project; + $this->branch = $branch; + $this->build = $build; + if ($this->build) { + $this->loadParentBuild($isParent); + } + if (defined('PHPCI_URL')) { + $this->setUrl(PHPCI_URL); + } + } + + /** + * @param $url + */ + public function setUrl($url) + { + $this->url = $url; + } + + /** + * @return Build + */ + public function getBuild() + { + return $this->build; + } + + /** + * @param bool $isParent + * @throws \Exception + */ + protected function loadParentBuild($isParent = true) + { + if ($isParent === false && !$this->isFinished()) { + $lastFinishedBuild = $this->project->getLatestBuild($this->branch, $this->finishedStatusIds); + + if ($lastFinishedBuild) { + $this->prevService = new BuildStatusService( + $this->branch, + $this->project, + $lastFinishedBuild, + true + ); + } + } + } + + /** + * @return string + */ + public function getActivity() + { + if (in_array($this->build->getStatus(), $this->finishedStatusIds)) { + return 'Sleeping'; + } elseif ($this->build->getStatus() == Build::STATUS_NEW) { + return 'Pending'; + } elseif ($this->build->getStatus() == Build::STATUS_RUNNING) { + return 'Building'; + } + return 'Unknown'; + } + + /** + * @return string + */ + public function getName() + { + return $this->project->getTitle() . ' / ' . $this->branch; + } + + /** + * @return bool + */ + public function isFinished() + { + if (in_array($this->build->getStatus(), $this->finishedStatusIds)) { + return true; + } + return false; + } + + /** + * @return null|Build + */ + public function getFinishedBuildInfo() + { + if ($this->isFinished()) { + return $this->build; + } elseif ($this->prevService) { + return $this->prevService->getBuild(); + } + return null; + } + + /** + * @return int|string + */ + public function getLastBuildLabel() + { + if ($buildInfo = $this->getFinishedBuildInfo()) { + return $buildInfo->getId(); + } + return ''; + } + + /** + * @return string + */ + public function getLastBuildTime() + { + $dateFormat = 'Y-m-d\\TH:i:sO'; + if ($buildInfo = $this->getFinishedBuildInfo()) { + return ($buildInfo->getFinished()) ? $buildInfo->getFinished()->format($dateFormat) : ''; + } + return ''; + } + + /** + * @param Build $build + * @return string + */ + public function getBuildStatus(Build $build) + { + switch ($build->getStatus()) { + case Build::STATUS_SUCCESS: + return 'Success'; + case Build::STATUS_FAILED: + return 'Failure'; + } + return 'Unknown'; + } + + /** + * @return string + */ + public function getLastBuildStatus() + { + if ($build = $this->getFinishedBuildInfo()) { + return $this->getBuildStatus($build); + } + return ''; + } + + /** + * @return string + */ + public function getBuildUrl() + { + return $this->url . 'build/view/' . $this->build->getId(); + } + + /** + * @return array + */ + public function toArray() + { + if (!$this->build) { + return array(); + } + return array( + 'name' => $this->getName(), + 'activity' => $this->getActivity(), + 'lastBuildLabel' => $this->getLastBuildLabel(), + 'lastBuildStatus' => $this->getLastBuildStatus(), + 'lastBuildTime' => $this->getLastBuildTime(), + 'webUrl' => $this->getBuildUrl(), + ); + } +} diff --git a/PHPCI/Store/BuildStore.php b/PHPCI/Store/BuildStore.php index 3a4c0ddc..7519a12f 100644 --- a/PHPCI/Store/BuildStore.php +++ b/PHPCI/Store/BuildStore.php @@ -107,6 +107,27 @@ class BuildStore extends BuildStoreBase } } + /** + * Returns all registered branches for project + * + * @param $projectId + * @return array + * @throws \Exception + */ + public function getBuildBranches($projectId) + { + $query = 'SELECT DISTINCT `branch` FROM `build` WHERE `project_id` = :project_id'; + $stmt = Database::getConnection('read')->prepare($query); + $stmt->bindValue(':project_id', $projectId); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_COLUMN); + return $res; + } else { + return array(); + } + } + /** * Return build metadata by key, project and optionally build id. * @param $key diff --git a/Tests/PHPCI/Service/BuiltStatusServiceTest.php b/Tests/PHPCI/Service/BuiltStatusServiceTest.php new file mode 100644 index 00000000..91e115e4 --- /dev/null +++ b/Tests/PHPCI/Service/BuiltStatusServiceTest.php @@ -0,0 +1,212 @@ + + */ +class BuildStatusServiceTest extends \PHPUnit_Framework_TestCase +{ + const BRANCH = 'master'; + + /** @var Project */ + protected $project; + + protected $timezone; + + public function setUp() + { + $project = new Project(); + $project->setId(3); + $project->setBranch(self::BRANCH); + $project->setTitle('Test'); + + $this->project = $project; + $this->timezone = date_default_timezone_get(); + + date_default_timezone_set('UTC'); + } + + public function tearDown() + { + date_default_timezone_set($this->timezone); + } + + /** + * @param $configId + * @param bool $setProject + * @return Build + */ + protected function getBuild($configId, $setProject = true) + { + $config = array( + '1' => array( + 'status' => Build::STATUS_RUNNING, + 'id' => 77, + 'finishDateTime' => null, + 'startedDate' => '2014-10-25 21:20:02', + 'previousBuild' => null, + ), + '2' => array( + 'status' => Build::STATUS_RUNNING, + 'id' => 78, + 'finishDateTime' => null, + 'startedDate' => '2014-10-25 21:20:02', + 'previousBuild' => 4, + ), + '3' => array( + 'status' => Build::STATUS_SUCCESS, + 'id' => 7, + 'finishDateTime' => '2014-10-25 21:50:02', + 'startedDate' => '2014-10-25 21:20:02', + 'previousBuild' => null, + ), + '4' => array( + 'status' => Build::STATUS_FAILED, + 'id' => 13, + 'finishDateTime' => '2014-10-13 13:13:13', + 'previousBuild' => null, + ), + '5' => array( + 'status' => Build::STATUS_NEW, + 'id' => 1000, + 'finishDateTime' => '2014-12-25 21:12:21', + 'previousBuild' => 3, + ) + ); + + $build = new Build(); + $build->setId($config[$configId]['id']); + $build->setBranch(self::BRANCH); + $build->setStatus($config[$configId]['status']); + if ($config[$configId]['finishDateTime']) { + $build->setFinished(new \DateTime($config[$configId]['finishDateTime'])); + } + if (!empty($config[$configId]['startedDate'])) { + $build->setStarted(new \DateTime('2014-10-25 21:20:02')); + } + + $project = $this->getProjectMock($config[$configId]['previousBuild'], $setProject); + + $build->setProjectObject($project); + + return $build; + } + + /** + * @param null|int $prevBuildId + * @param bool $setProject + * @return Project + */ + protected function getProjectMock($prevBuildId = null, $setProject = true) { + + $project = $this->getMock('PHPCI\Model\Project', array('getLatestBuild')); + + $prevBuild = ($prevBuildId) ? $this->getBuild($prevBuildId, false) : null; + + $project->expects($this->any()) + ->method('getLatestBuild') + ->will($this->returnValue($prevBuild)); + + /* @var $project Project */ + + $project->setId(3); + $project->setBranch(self::BRANCH); + $project->setTitle('Test'); + + if ($setProject) { + $this->project = $project; + } + + return $project; + + } + + /** + * @dataProvider finishedProvider + * + * @param int $buildConfigId + * @param array $expectedResult + */ + public function testFinished($buildConfigId, array $expectedResult) + { + $build = $this->getBuild($buildConfigId); + $service = new BuildStatusService(self::BRANCH, $this->project, $build); + $service->setUrl('http://phpci.dev/'); + $this->assertEquals($expectedResult, $service->toArray()); + } + + public function finishedProvider() + { + return array( + 'buildingStatus' => array( + 1, + array( + 'name' => 'Test / master', + 'activity' => 'Building', + 'lastBuildLabel' => '', + 'lastBuildStatus' => '', + 'lastBuildTime' => '', + 'webUrl' => 'http://phpci.dev/build/view/77', + ) + ), + 'buildingStatusWithPrev' => array( + 2, + array( + 'name' => 'Test / master', + 'activity' => 'Building', + 'lastBuildLabel' => 13, + 'lastBuildStatus' => 'Failure', + 'lastBuildTime' => '2014-10-13T13:13:13+0000', + 'webUrl' => 'http://phpci.dev/build/view/78', + ) + ), + 'successStatus' => array( + 3, + array( + 'name' => 'Test / master', + 'activity' => 'Sleeping', + 'lastBuildLabel' => 7, + 'lastBuildStatus' => 'Success', + 'lastBuildTime' => '2014-10-25T21:50:02+0000', + 'webUrl' => 'http://phpci.dev/build/view/7', + ) + ), + 'failureStatus' => array( + 4, + array( + 'name' => 'Test / master', + 'activity' => 'Sleeping', + 'lastBuildLabel' => 13, + 'lastBuildStatus' => 'Failure', + 'lastBuildTime' => '2014-10-13T13:13:13+0000', + 'webUrl' => 'http://phpci.dev/build/view/13', + ) + ), + 'pending' => array( + 5, + array( + 'name' => 'Test / master', + 'activity' => 'Pending', + 'lastBuildLabel' => 7, + 'lastBuildStatus' => 'Success', + 'lastBuildTime' => '2014-10-25T21:50:02+0000', + 'webUrl' => 'http://phpci.dev/build/view/1000', + ) + ), + ); + } +} \ No newline at end of file