Track and display the build progression, for each stages and plugins.

Translations for the build summary.

Closed #944
This commit is contained in:
Adirelle 2015-04-24 19:33:13 +02:00 committed by Tobias van Beek
parent 8549ba30cf
commit ad29ba4cfd
7 changed files with 192 additions and 33 deletions

View file

@ -295,6 +295,17 @@ PHPCI',
'search_packagist_for_more' => 'Search Packagist for more packages',
'search' => 'Search »',
// Summary plugin
'build-summary' => 'Summary',
'stage' => 'Stage',
'duration' => 'Duration',
'plugin' => 'Plugin',
'stage_setup' => 'Setup',
'stage_test' => 'Test',
'stage_complete' => 'Complete',
'stage_success' => 'Success',
'stage_failure' => 'Failure',
// Installer
'installation_url' => 'PHPCI Installation URL',
'db_host' => 'Database Host',

View file

@ -286,6 +286,17 @@ PHPCI',
'search_packagist_for_more' => 'Rechercher sur Packagist pour trouver plus de paquets',
'search' => 'Rechercher »',
// Summary plugin
'build-summary' => 'Résumé',
'stage' => 'Étape',
'duration' => 'Durée',
'plugin' => 'Plugin',
'stage_setup' => 'Préparation',
'stage_test' => 'Test',
'stage_complete' => 'Terminé',
'stage_success' => 'Succes',
'stage_failure' => 'Échec',
// Installer
'installation_url' => 'URL d\'installation de PHPCI',
'db_host' => 'Hôte de la BDD',

View file

@ -2,8 +2,12 @@
namespace PHPCI\Plugin\Util;
use b8\Store\Factory as StoreFactory;
use Exception;
use PHPCI\Helper\Lang;
use \PHPCI\Logging\BuildLogger;
use PHPCI\Logging\BuildLogger;
use PHPCI\Model\Build;
use PHPCI\Store\BuildStore;
/**
* Plugin Executor - Runs the configured plugins for a given build stage.
@ -21,14 +25,20 @@ class Executor
*/
protected $pluginFactory;
/**
* @var BuildStore
*/
protected $store;
/**
* @param Factory $pluginFactory
* @param BuildLogger $logger
*/
public function __construct(Factory $pluginFactory, BuildLogger $logger)
public function __construct(Factory $pluginFactory, BuildLogger $logger, BuildStore $store = null)
{
$this->pluginFactory = $pluginFactory;
$this->logger = $logger;
$this->store = $store ?: StoreFactory::getStore('Build');
}
/**
@ -48,22 +58,29 @@ class Executor
foreach ($config[$stage] as $plugin => $options) {
$this->logger->log(Lang::get('running_plugin', $plugin));
// Try and execute it:
if ($this->executePlugin($plugin, $options)) {
// Execution was successful:
$this->logger->logSuccess(Lang::get('plugin_success'));
} elseif ($stage == 'setup') {
// If we're in the "setup" stage, execution should not continue after
// a plugin has failed:
throw new \Exception('Plugin failed: ' . $plugin);
} else {
// If we're in the "test" stage and the plugin is not allowed to fail,
// then mark the build as failed:
if ($stage == 'test' && (!isset($options['allow_failures']) || !$options['allow_failures'])) {
$success = false;
}
$this->setPluginStatus($stage, $plugin, Build::STATUS_RUNNING);
// Try and execute it
if ($this->executePlugin($plugin, $options)) {
// Execution was successful
$this->logger->logSuccess(Lang::get('plugin_success'));
$this->setPluginStatus($stage, $plugin, Build::STATUS_SUCCESS);
} else {
// Execution failed
$this->logger->logFailure(Lang::get('plugin_failed'));
$this->setPluginStatus($stage, $plugin, Build::STATUS_FAILED);
if ($stage === 'setup') {
// If we're in the "setup" stage, execution should not continue after
// a plugin has failed:
throw new Exception('Plugin failed: ' . $plugin);
} elseif ($stage === 'test') {
// If we're in the "test" stage and the plugin is not allowed to fail,
// then mark the build as failed:
if (empty($options['allow_failures'])) {
$success = false;
}
}
}
}
@ -91,20 +108,62 @@ class Executor
return false;
}
$rtn = true;
// Try running it:
try {
// Build and run it
$obj = $this->pluginFactory->buildPlugin($class, $options);
if (!$obj->execute()) {
$rtn = false;
}
return $obj->execute();
} catch (\Exception $ex) {
$this->logger->logFailure(Lang::get('exception') . $ex->getMessage(), $ex);
$rtn = false;
return false;
}
}
/**
* Change the status of a plugin for a given stage.
*
* @param string $stage The builder stage.
* @param string $plugin The plugin name.
* @param int $status The new status.
*/
protected function setPluginStatus($stage, $plugin, $status)
{
$summary = $this->getBuildSummary();
if (!isset($summary[$stage][$plugin])) {
$summary[$stage][$plugin] = array();
}
return $rtn;
$summary[$stage][$plugin]['status'] = $status;
if ($status === Build::STATUS_RUNNING) {
$summary[$stage][$plugin]['started'] = time();
} elseif ($status >= Build::STATUS_SUCCESS) {
$summary[$stage][$plugin]['ended'] = time();
}
$this->setBuildSummary($summary);
}
/**
* Fetch the summary data of the current build.
*
* @return array
*/
private function getBuildSummary()
{
$build = $this->pluginFactory->getResourceFor('PHPCI\Model\Build');
$metas = $this->store->getMeta('plugin-summary', $build->getProjectId(), $build->getId());
return isset($metas[0]['meta_value']) ? $metas[0]['meta_value'] : array();
}
/**
* Sets the summary data of the current build.
*
* @param array summary
*/
private function setBuildSummary($summary)
{
$build = $this->pluginFactory->getResourceFor('PHPCI\Model\Build');
$this->store->setMeta($build->getProjectId(), $build->getId(), 'plugin-summary', json_encode($summary));
}
}

View file

@ -150,11 +150,11 @@ class Factory
}
/**
* @param null $type
* @param null $name
* @return null
* @param string $type
* @param string $name
* @return mixed
*/
private function getResourceFor($type = null, $name = null)
public function getResourceFor($type = null, $name = null)
{
$fullId = $this->getInternalID($type, $name);
if (isset($this->container[$fullId])) {

View file

@ -165,7 +165,9 @@ class BuildStore extends BuildStoreBase
$stmt->bindValue(':projectId', (int)$projectId, \PDO::PARAM_INT);
$stmt->bindValue(':buildId', (int)$buildId, \PDO::PARAM_INT);
$stmt->bindValue(':numResults', (int)$numResults, \PDO::PARAM_INT);
$stmt->bindValue(':branch', $branch, \PDO::PARAM_STR);
if (!is_null($branch)) {
$stmt->bindValue(':branch', $branch, \PDO::PARAM_STR);
}
if ($stmt->execute()) {
$rtn = $stmt->fetchAll(\PDO::FETCH_ASSOC);

View file

@ -24,12 +24,19 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
protected $mockFactory;
protected $mockStore;
protected function setUp()
{
parent::setUp();
$this->mockBuildLogger = $this->prophesize('\PHPCI\Logging\BuildLogger');
$this->mockFactory = $this->prophesize('\PHPCI\Plugin\Util\Factory');
$this->testedExecutor = new Executor($this->mockFactory->reveal(), $this->mockBuildLogger->reveal());
$this->mockStore = $this->prophesize('\PHPCI\Store\BuildStore');
$this->testedExecutor = new Executor(
$this->mockFactory->reveal(),
$this->mockBuildLogger->reveal(),
$this->mockStore->reveal()
);
}
public function testExecutePlugin_AssumesPHPCINamespaceIfNoneGiven()
@ -61,11 +68,13 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
{
$options = array();
$pluginName = 'PhpUnit';
$build = new \PHPCI\Model\Build();
$mockPlugin = $this->prophesize('PHPCI\Plugin');
$mockPlugin->execute()->shouldBeCalledTimes(1);
$this->mockFactory->buildPlugin(Argument::any(), Argument::any())->willReturn($mockPlugin->reveal());
$this->mockFactory->getResourceFor('PHPCI\Model\Build')->willReturn($build);
$this->testedExecutor->executePlugin($pluginName, $options);
}
@ -119,6 +128,7 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
{
$phpUnitPluginOptions = array();
$behatPluginOptions = array();
$build = new \PHPCI\Model\Build();
$config = array(
'stageOne' => array(
@ -134,7 +144,7 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
$this->mockFactory->buildPlugin($pluginNamespace . 'PhpUnit', $phpUnitPluginOptions)
->willReturn($mockPhpUnitPlugin->reveal());
$this->mockFactory->getResourceFor('PHPCI\Model\Build')->willReturn($build);
$mockBehatPlugin = $this->prophesize('PHPCI\Plugin');
$mockBehatPlugin->execute()->shouldBeCalledTimes(1)->willReturn(true);
@ -142,7 +152,6 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
$this->mockFactory->buildPlugin($pluginNamespace . 'Behat', $behatPluginOptions)
->willReturn($mockBehatPlugin->reveal());
$this->testedExecutor->executePlugins($config, 'stageOne');
}

View file

@ -0,0 +1,67 @@
var SummaryPlugin = ActiveBuild.UiPlugin.extend({
id: 'build-summary',
css: 'col-lg-6 col-md-12 col-sm-12 col-xs-12',
title: Lang.get('build-summary'),
box: true,
statusIcons: [ 'fa-clock-o', 'fa-cogs', 'fa-check', 'fa-remove' ],
statusLabels: [ Lang.get('pending'), Lang.get('running'), Lang.get('successful'), Lang.get('failed') ],
statusClasses: ['text-blue', 'text-yellow', 'text-green', 'text-red'],
register: function() {
var self = this;
var query = ActiveBuild.registerQuery('plugin-summary', 5, {key: 'plugin-summary'})
$(window).on('plugin-summary', function(data) {
self.onUpdate(data);
});
$(window).on('build-updated', function() {
query();
});
},
render: function() {
return $(
'<div class="table-responsive"><table class="table" id="plugin-summary">' +
'<thead><tr>' +
'<th>'+Lang.get('stage')+'</th>' +
'<th>'+Lang.get('plugin')+'</th>' +
'<th>'+Lang.get('status')+'</th>' +
'<th class="text-right">'+Lang.get('duration')+' (s)</th>' +
'</tr></thead><tbody></tbody></table></div>'
);
},
onUpdate: function(e) {
if (!e.queryData) {
$('#build-summary').hide();
return;
}
var tbody = $('#plugin-summary tbody'),
summary = e.queryData[0].meta_value;
tbody.empty();
for(var stage in summary) {
for(var plugin in summary[stage]) {
var data = summary[stage][plugin],
duration = data.started ? ((data.ended || Math.floor(Date.now()/1000)) - data.started) : '-';
tbody.append(
'<tr>' +
'<td>' + Lang.get('stage_'+stage) + '</td>' +
'<td>' + plugin + '</td>' +
'<td><span class="' + this.statusClasses[data.status] + '">' +
'<i class="fa ' + this.statusIcons[data.status] + '"></i>&nbsp;' +
this.statusLabels[data.status] +
'</span></td>' +
'<td class="text-right">' + duration + '</td>' +
'</tr>'
);
}
}
$('#build-summary').show();
}
});
ActiveBuild.registerPlugin(new SummaryPlugin());