UI plugins, including quality trend chart, logs and lines of code. Some UI tweaks.

This commit is contained in:
Dan Cryer 2013-10-08 07:21:46 +01:00
parent a634f6086b
commit b33189e08e
28 changed files with 2775 additions and 214 deletions

1
.gitignore vendored
View file

@ -1,6 +1,5 @@
.idea
vendor/
composer.lock
composer.phar
config.php
.DS_Store

View file

@ -73,7 +73,12 @@ class Builder
* @var array
*/
protected $config;
/**
* @var string
*/
protected $lastOutput;
/**
* An array of key => value pairs that will be used for
* interpolation and environment variables
@ -83,6 +88,11 @@ class Builder
*/
protected $interpolation_vars = array();
/**
* @var \PHPCI\Store\BuildStore
*/
protected $store;
/**
* Set up the builder.
* @param \PHPCI\Model\Build
@ -214,17 +224,24 @@ class Builder
$this->log('Executing: ' . $command, ' ');
$output = '';
$status = 0;
exec($command, $output, $status);
exec($command, $this->lastOutput, $status);
if (!empty($output) && ($this->verbose || $status != 0)) {
$this->log($output, ' ');
if (!empty($this->lastOutput) && ($this->verbose || $status != 0)) {
$this->log($this->lastOutput, ' ');
}
return ($status == 0) ? true : false;
}
/**
* Returns the output from the last command run.
*/
public function getLastOutput()
{
return implode(PHP_EOL, $this->lastOutput);
}
/**
* Add an entry to the build log.
* @param string|string[]
@ -433,4 +450,13 @@ class Builder
$this->log('Removing build.');
shell_exec(sprintf('rm -Rf "%s"', $this->buildPath));
}
/**
* Store build meta data
*/
public function storeBuildMeta($key, $value)
{
$value = json_encode($value);
$this->store->setMeta($this->build->getProjectId(), $this->build->getId(), $key, $value);
}
}

View file

@ -31,10 +31,28 @@ class BuildController extends \PHPCI\Controller
public function view($buildId)
{
$build = $this->_buildStore->getById($buildId);
$this->view->plugins = $this->getUiPlugins();
$this->view->build = $build;
$this->view->data = $this->getBuildData($buildId);
}
protected function getUiPlugins()
{
$rtn = array();
$path = APPLICATION_PATH . 'public/assets/js/build-plugins/';
$dir = opendir($path);
while ($item = readdir($dir)) {
if (substr($item, 0, 1) == '.' || substr($item, -3) != '.js') {
continue;
}
$rtn[] = $item;
}
return $rtn;
}
/**
* AJAX call to get build data:
*/
@ -43,6 +61,23 @@ class BuildController extends \PHPCI\Controller
die($this->getBuildData($buildId));
}
/**
* AJAX call to get build meta:
*/
public function meta($buildId)
{
$build = $this->_buildStore->getById($buildId);
$key = $this->getParam('key', null);
$numBuilds = $this->getParam('num_builds', 1);
$data = null;
if ($key && $build) {
$data = $this->_buildStore->getMeta($key, $build->getProjectId(), $buildId, $numBuilds);
}
die(json_encode($data));
}
/**
* Get build data from database and json encode it:
*/

View file

@ -579,4 +579,16 @@ class BuildBase extends Model
{
return $this->setProjectId($value->getId());
}
/**
* Get BuildMeta models by BuildId for this Build.
*
* @uses \PHPCI\Store\BuildMetaStore::getByBuildId()
* @uses \PHPCI\Model\BuildMeta
* @return \PHPCI\Model\BuildMeta[]
*/
public function getBuildBuildMetas()
{
return \b8\Store\Factory::getStore('BuildMeta')->getByBuildId($this->getId());
}
}

View file

@ -0,0 +1,293 @@
<?php
/**
* BuildMeta base model for table: build_meta
*/
namespace PHPCI\Model\Base;
use b8\Model;
/**
* BuildMeta Base Model
*/
class BuildMetaBase extends Model
{
/**
* @var array
*/
public static $sleepable = array();
/**
* @var string
*/
protected $tableName = 'build_meta';
/**
* @var string
*/
protected $modelName = 'BuildMeta';
/**
* @var array
*/
protected $data = array(
'id' => null,
'build_id' => null,
'key' => null,
'value' => null,
);
/**
* @var array
*/
protected $getters = array(
'id' => 'getId',
'build_id' => 'getBuildId',
'key' => 'getKey',
'value' => 'getValue',
'Build' => 'getBuild',
);
/**
* @var array
*/
protected $setters = array(
'id' => 'setId',
'build_id' => 'setBuildId',
'key' => 'setKey',
'value' => 'setValue',
'Build' => 'setBuild',
);
/**
* @var array
*/
public $columns = array(
'id' => array(
'type' => 'int',
'length' => '10',
'primary_key' => true,
'auto_increment' => true,
),
'build_id' => array(
'type' => 'int',
'length' => '11',
),
'key' => array(
'type' => 'varchar',
'length' => '255',
),
'value' => array(
'type' => 'text',
'length' => '',
'nullable' => true,
),
);
/**
* @var array
*/
public $indexes = array(
'PRIMARY' => array('unique' => true, 'columns' => 'id'),
'idx_meta_id' => array('unique' => true, 'columns' => 'build_id, key'),
);
/**
* @var array
*/
public $foreignKeys = array(
'fk_meta_build_id' => array(
'local_col' => 'build_id',
'update' => 'CASCADE',
'delete' => 'CASCADE',
'table' => 'build',
'col' => 'id'
),
);
/**
* Get the value of Id / id.
*
* @return int
*/
public function getId()
{
$rtn = $this->data['id'];
return $rtn;
}
/**
* Get the value of BuildId / build_id.
*
* @return int
*/
public function getBuildId()
{
$rtn = $this->data['build_id'];
return $rtn;
}
/**
* Get the value of Key / key.
*
* @return string
*/
public function getKey()
{
$rtn = $this->data['key'];
return $rtn;
}
/**
* Get the value of Value / value.
*
* @return string
*/
public function getValue()
{
$rtn = $this->data['value'];
return $rtn;
}
/**
* Set the value of Id / id.
*
* Must not be null.
* @param $value int
*/
public function setId($value)
{
$this->_validateNotNull('Id', $value);
$this->_validateInt('Id', $value);
if ($this->data['id'] == $value) {
return;
}
$this->data['id'] = $value;
$this->_setModified('id');
}
/**
* Set the value of BuildId / build_id.
*
* Must not be null.
* @param $value int
*/
public function setBuildId($value)
{
$this->_validateNotNull('BuildId', $value);
$this->_validateInt('BuildId', $value);
if ($this->data['build_id'] == $value) {
return;
}
$this->data['build_id'] = $value;
$this->_setModified('build_id');
}
/**
* Set the value of Key / key.
*
* Must not be null.
* @param $value string
*/
public function setKey($value)
{
$this->_validateNotNull('Key', $value);
$this->_validateString('Key', $value);
if ($this->data['key'] == $value) {
return;
}
$this->data['key'] = $value;
$this->_setModified('key');
}
/**
* Set the value of Value / value.
*
* @param $value string
*/
public function setValue($value)
{
$this->_validateString('Value', $value);
if ($this->data['value'] == $value) {
return;
}
$this->data['value'] = $value;
$this->_setModified('value');
}
/**
* Get the Build model for this BuildMeta by Id.
*
* @uses \PHPCI\Store\BuildStore::getById()
* @uses \PHPCI\Model\Build
* @return \PHPCI\Model\Build
*/
public function getBuild()
{
$key = $this->getBuildId();
if (empty($key)) {
return null;
}
$cacheKey = 'Cache.Build.' . $key;
$rtn = $this->cache->get($cacheKey, null);
if (empty($rtn)) {
$rtn = \b8\Store\Factory::getStore('Build')->getById($key);
$this->cache->set($cacheKey, $rtn);
}
return $rtn;
}
/**
* Set Build - Accepts an ID, an array representing a Build or a Build model.
*
* @param $value mixed
*/
public function setBuild($value)
{
// Is this an instance of Build?
if ($value instanceof \PHPCI\Model\Build) {
return $this->setBuildObject($value);
}
// Is this an array representing a Build item?
if (is_array($value) && !empty($value['id'])) {
return $this->setBuildId($value['id']);
}
// Is this a scalar value representing the ID of this foreign key?
return $this->setBuildId($value);
}
/**
* Set Build - Accepts a Build model.
*
* @param $value \PHPCI\Model\Build
*/
public function setBuildObject(\PHPCI\Model\Build $value)
{
return $this->setBuildId($value->getId());
}
}

View file

@ -34,11 +34,11 @@ class ProjectBase extends Model
protected $data = array(
'id' => null,
'title' => null,
'access_information' => null,
'reference' => null,
'git_key' => null,
'type' => null,
'token' => null,
'access_information' => null,
);
/**
@ -50,8 +50,8 @@ class ProjectBase extends Model
'reference' => 'getReference',
'git_key' => 'getGitKey',
'type' => 'getType',
'access_information' => 'getAccessInformation',
'token' => 'getToken',
'access_information' => 'getAccessInformation',
);
/**
@ -63,8 +63,8 @@ class ProjectBase extends Model
'reference' => 'setReference',
'git_key' => 'setGitKey',
'type' => 'setType',
'access_information' => 'setAccessInformation',
'token' => 'setToken',
'access_information' => 'setAccessInformation',
);
/**
@ -85,10 +85,6 @@ class ProjectBase extends Model
'type' => 'varchar',
'length' => '250',
),
'access_information' => array(
'type' => 'varchar',
'length' => '250',
),
'git_key' => array(
'type' => 'text',
'length' => '',
@ -102,6 +98,10 @@ class ProjectBase extends Model
'length' => '50',
'nullable' => true,
),
'access_information' => array(
'type' => 'varchar',
'length' => '250',
),
);
/**
@ -157,19 +157,6 @@ class ProjectBase extends Model
return $rtn;
}
/**
* Get the value of Domain / domain.
*
* @return string
*/
public function getAccessInformation()
{
$rtn = unserialize($this->data['access_information']);
return $rtn;
}
/**
* Get the value of GitKey / git_key.
*
@ -209,6 +196,19 @@ class ProjectBase extends Model
return $rtn;
}
/**
* Get the value of AccessInformation / access_information.
*
* @return string
*/
public function getAccessInformation()
{
$rtn = $this->data['access_information'];
return $rtn;
}
/**
* Set the value of Id / id.
*
@ -266,25 +266,6 @@ class ProjectBase extends Model
$this->_setModified('reference');
}
/**
* Set the value of Domain / domain.
*
* Must not be null.
* @param $value string
*/
public function setAccessInformation($value)
{
$this->_validateNotNull('AccessInformation', $value);
$this->_validateString('AccessInformation', $value);
if ($this->data['access_information'] == $value) {
return;
}
$this->data['access_information'] = $value;
$this->_setModified('access_information');
}
/**
* Set the value of GitKey / git_key.
*
@ -341,6 +322,25 @@ class ProjectBase extends Model
$this->_setModified('token');
}
/**
* Set the value of AccessInformation / access_information.
*
* Must not be null.
* @param $value string
*/
public function setAccessInformation($value)
{
$this->_validateNotNull('AccessInformation', $value);
$this->_validateString('AccessInformation', $value);
if ($this->data['access_information'] == $value) {
return;
}
$this->data['access_information'] = $value;
$this->_setModified('access_information');
}
/**
* Get Build models by ProjectId for this Project.
*

20
PHPCI/Model/BuildMeta.php Normal file
View file

@ -0,0 +1,20 @@
<?php
/**
* BuildMeta model for table: build_meta
*/
namespace PHPCI\Model;
require_once(APPLICATION_PATH . 'PHPCI/Model/Base/BuildMetaBase.php');
use PHPCI\Model\Base\BuildMetaBase;
/**
* BuildMeta Model
* @uses PHPCI\Model\Base\BuildMetaBase
*/
class BuildMeta extends BuildMetaBase
{
// This class has been left blank so that you can modify it - changes in this file will not be overwritten.
}

View file

@ -106,6 +106,20 @@ class PhpCodeSniffer implements \PHPCI\Plugin
}
$cmd = PHPCI_BIN_DIR . 'phpcs %s %s %s %s %s "%s"';
return $this->phpci->executeCommand($cmd, $standard, $suffixes, $ignore, $tab_width, $encoding, $this->phpci->buildPath . $this->path);
$success = $this->phpci->executeCommand($cmd, $standard, $suffixes, $ignore, $tab_width, $encoding, $this->phpci->buildPath . $this->path);
$output = $this->phpci->getLastOutput();
$matches = array();
if (preg_match_all('/WARNING/', $output, $matches)) {
$this->phpci->storeBuildMeta('phpcs-warnings', count($matches[0]));
}
$matches = array();
if (preg_match_all('/ERROR/', $output, $matches)) {
$this->phpci->storeBuildMeta('phpcs-errors', count($matches[0]));
}
return $success;
}
}

View file

@ -57,6 +57,10 @@ class PhpCpd implements \PHPCI\Plugin
$ignore = implode('', $ignore);
}
return $this->phpci->executeCommand(PHPCI_BIN_DIR . 'phpcpd %s "%s"', $ignore, $this->phpci->buildPath.$this->path);
$success = $this->phpci->executeCommand(PHPCI_BIN_DIR . 'phpcpd %s "%s"', $ignore, $this->phpci->buildPath.$this->path);
print $this->phpci->getLastOutput();
return $success;
}
}

View file

@ -47,6 +47,18 @@ class PhpLoc implements \PHPCI\Plugin
$ignore = implode('', $ignore);
}
return $this->phpci->executeCommand(PHPCI_BIN_DIR . 'phploc %s "%s"', $ignore, $this->phpci->buildPath);
$success = $this->phpci->executeCommand(PHPCI_BIN_DIR . 'phploc %s "%s"', $ignore, $this->phpci->buildPath);
$output = $this->phpci->getLastOutput();
if (preg_match_all('/\((LOC|CLOC|NCLOC|LLOC)\)\s+([0-9]+)/', $output, $matches)) {
$data = array();
foreach ($matches[1] as $k => $v) {
$data[$v] = (int)$matches[2][$k];
}
$this->phpci->storeBuildMeta('phploc', $data);
}
return $success;
}
}

View file

@ -83,6 +83,10 @@ class PhpMessDetector implements \PHPCI\Plugin
}
$cmd = PHPCI_BIN_DIR . 'phpmd "%s" text %s %s %s';
return $this->phpci->executeCommand($cmd, $this->phpci->buildPath . $this->path, implode(',', $this->rules), $ignore, $suffixes);
$success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath . $this->path, implode(',', $this->rules), $ignore, $suffixes);
$errors = count(array_filter(explode(PHP_EOL, $this->phpci->getLastOutput())));
$this->phpci->storeBuildMeta('phpmd-warnings', $errors);
return $success;
}
}

View file

@ -0,0 +1,86 @@
<?php
/**
* BuildMeta base store for table: build_meta
*/
namespace PHPCI\Store\Base;
use b8\Store;
/**
* BuildMeta Base Store
*/
class BuildMetaStoreBase extends Store
{
protected $tableName = 'build_meta';
protected $modelName = '\PHPCI\Model\BuildMeta';
protected $primaryKey = 'id';
public function getByPrimaryKey($value, $useConnection = 'read')
{
return $this->getById($value, $useConnection);
}
public function getById($value, $useConnection = 'read')
{
if (is_null($value)) {
throw new \b8\Exception\HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.');
}
$query = 'SELECT * FROM build_meta WHERE id = :id LIMIT 1';
$stmt = \b8\Database::getConnection($useConnection)->prepare($query);
$stmt->bindValue(':id', $value);
if ($stmt->execute()) {
if ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) {
return new \PHPCI\Model\BuildMeta($data);
}
}
return null;
}
public function getByBuildId($value, $limit = null, $useConnection = 'read')
{
if (is_null($value)) {
throw new \b8\Exception\HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.');
}
$add = '';
if ($limit) {
$add .= ' LIMIT ' . $limit;
}
$query = 'SELECT COUNT(*) AS cnt FROM build_meta WHERE build_id = :build_id' . $add;
$stmt = \b8\Database::getConnection($useConnection)->prepare($query);
$stmt->bindValue(':build_id', $value);
if ($stmt->execute()) {
$res = $stmt->fetch(\PDO::FETCH_ASSOC);
$count = (int)$res['cnt'];
} else {
$count = 0;
}
$query = 'SELECT * FROM build_meta WHERE build_id = :build_id' . $add;
$stmt = \b8\Database::getConnection('read')->prepare($query);
$stmt->bindValue(':build_id', $value);
if ($stmt->execute()) {
$res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$map = function ($item) {
return new \PHPCI\Model\BuildMeta($item);
};
$rtn = array_map($map, $res);
return array('items' => $rtn, 'count' => $count);
} else {
return array('items' => array(), 'count' => 0);
}
}
}

View file

@ -0,0 +1,20 @@
<?php
/**
* BuildMeta store for table: build_meta
*/
namespace PHPCI\Store;
require_once(APPLICATION_PATH . 'PHPCI/Store/Base/BuildMetaStoreBase.php');
use PHPCI\Store\Base\BuildMetaStoreBase;
/**
* BuildMeta Store
* @uses PHPCI\Store\Base\BuildMetaStoreBase
*/
class BuildMetaStore extends BuildMetaStoreBase
{
// This class has been left blank so that you can modify it - changes in this file will not be overwritten.
}

View file

@ -47,4 +47,52 @@ class BuildStore extends BuildStoreBase
return array('items' => array(), 'count' => 0);
}
}
public function getMeta($key, $projectId, $buildId = null, $numResults = 1)
{
$and = $numResults > 1 ? ' AND (`build_id` <= :buildId) ' : ' AND (`build_id` = :buildId) ';
$query = 'SELECT `build_id`, `key`, `value` FROM `build_meta` WHERE `key` = :key AND `project_id` = :projectId ' . $and . ' ORDER BY id DESC LIMIT :numResults';
$stmt = \b8\Database::getConnection('read')->prepare($query);
$stmt->bindValue(':key', $key, \PDO::PARAM_STR);
$stmt->bindValue(':projectId', (int)$projectId, \PDO::PARAM_INT);
$stmt->bindValue(':buildId', (int)$buildId, \PDO::PARAM_INT);
$stmt->bindValue(':numResults', (int)$numResults, \PDO::PARAM_INT);
if ($stmt->execute()) {
$rtn = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$rtn = array_reverse($rtn);
$rtn = array_map(function($item) {
$item['value'] = json_decode($item['value'], true);
return $item;
}, $rtn);
if (!count($rtn)) {
return null;
} else {
return $rtn;
}
} else {
return null;
}
}
public function setMeta($projectId, $buildId, $key, $value)
{
$query = 'REPLACE INTO build_meta (project_id, build_id, `key`, `value`) VALUES (:projectId, :buildId, :key, :value)';
$stmt = \b8\Database::getConnection('read')->prepare($query);
$stmt->bindValue(':key', $key, \PDO::PARAM_STR);
$stmt->bindValue(':projectId', (int)$projectId, \PDO::PARAM_INT);
$stmt->bindValue(':buildId', (int)$buildId, \PDO::PARAM_INT);
$stmt->bindValue(':value', $value, \PDO::PARAM_STR);
if ($stmt->execute()) {
return true;
} else {
return false;
}
}
}

View file

@ -16,68 +16,27 @@
<li><a href="#" id="delete-build"><i class="icon-trash"></i> Delete Build</a></li>
<?php endif; ?>
</ul>
<table class="table table-striped table-bordered" style="margin-top: 20px">
<thead>
<tr>
<th colspan="2">Plugin Status</th>
</tr>
</thead>
<tbody id="plugins">
</tbody>
</table>
</div>
<div class="col-lg-9">
<div id="status"></div>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th style="width: 33.3%">Build Created</th>
<th style="width: 33.3%">Build Started</th>
<th style="width: 33.3%">Build Finished</th>
</tr>
</thead>
<tbody>
<tr>
<td id="created"></td>
<td id="started"></td>
<td id="finished"></td>
</tr>
</tbody>
</table>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Build Log</th>
</tr>
</thead>
<tbody>
<tr>
<td><pre style="height: 300px; overflow-y: auto;" id="log"></pre></td>
</tr>
</tbody>
</table>
</div>
<div id="plugins"></div>
</div>
</div>
<script>
window.initial = <?php print $data; ?>;
var PHPCI = new PHPCIObject(<?php print $build->getId() ?>);
PHPCI.buildData = <?php print $data; ?>;
</script>
<?php if($build->getStatus() == 0 || $build->getStatus() == 1 || true): ?>
setInterval(function()
{
$.getJSON('<?= PHPCI_URL ?>build/data/<?php print $build->getId(); ?>', updateBuildView);
}, 10000);
<?php endif; ?>
<?php
foreach ($plugins as $plugin) {
print '<script src="'.PHPCI_URL.'assets/js/build-plugins/' . $plugin . '"></script>';
}
?>
$(function() {
updateBuildView(window.initial);
$('#delete-build').on('click', function (e) {
e.preventDefault();
confirmDelete("<?= PHPCI_URL ?>build/delete/<?php print $build->getId(); ?>");
});
});
<script>
$(document).ready(function() {
PHPCI.renderPlugins();
});
</script>

View file

@ -41,7 +41,8 @@
<?php endif; ?>
</div>
<div class="col-lg-9">
<h3>Project Overview</h3>
<div class="box">
<h3 class="title">Project Overview</h3>
<table class="table table-striped table-bordered">
<thead>
<tr>
@ -57,8 +58,10 @@
<?php print $summary; ?>
</tbody>
</table>
</div>
<h3>Last 5 Builds</h3>
<div class="box">
<h3 class="title">Last 5 Builds</h3>
<table class="table table-striped table-bordered">
<thead>
<tr>
@ -74,6 +77,7 @@
<?php print $builds; ?>
</tbody>
</table>
</div>
</div>
</div>

View file

@ -17,7 +17,9 @@
</div>
</div>
<div class="col-lg-8">
<div class="box">
<?php print $form; ?>
</div>
</div>
</div>

View file

@ -13,6 +13,8 @@
</div>
</div>
<div class="col-lg-8">
<div class="box">
<?php print $form; ?>
</div>
</div>
</div>

View file

@ -9,9 +9,16 @@
<link href='//fonts.googleapis.com/css?family=Roboto:300&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="<?= PHPCI_URL ?>assets/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="<?= PHPCI_URL ?>assets/css/phpci.css">
<script>window.PHPCI_URL = <?php print json_encode(PHPCI_URL) ?></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="<?= PHPCI_URL ?>assets/js/bootstrap.min.js"></script>
<script src="<?= PHPCI_URL ?>assets/js/phpci.js"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script src="<?= PHPCI_URL ?>assets/js/bootstrap.min.js"></script>
<script src="<?= PHPCI_URL ?>assets/js/jqueryui.js"></script>
<script src="<?= PHPCI_URL ?>assets/js/class.js"></script>
<script src="<?= PHPCI_URL ?>assets/js/phpci.js"></script>
</head>
<body>
<div class="navbar navbar-fixed-top">

1663
composer.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
body
{
background: #246;
background: #f9f9f9;
font-family: Roboto, Arial, Sans-Serif;
font-style: normal;
font-weight: 300;
@ -9,8 +9,7 @@ body
#content
{
background: #fff;
border: 10px solid #369;
-border: 10px solid #369;
padding: 10px;
}
@ -50,7 +49,6 @@ td .label {
#title
{
background: #f8f8f8;
border-bottom: 1px solid #ccc;
margin: -10px -10px 15px -10px;
padding: 10px;
@ -92,4 +90,41 @@ h3
.navbar-brand {
padding: 10px 15px;
}
}
.box {
background: #fff;
box-shadow: 0 0 5px rgba(0,0,0,0.1);
margin-bottom: 15px;
padding: 10px;
}
.box .title {
border-bottom: 1px solid #eee;
cursor: move;
font-size: 1.2em;
padding: 8px;
}
.box .box-content {
}
.box .box-content table, .box .box-content pre {
margin-bottom: 0;
}
.box .box-content pre {
background: #fff;
border: 0;
}
.ui-sortable-placeholder {
border: 1px dashed #ccc;
background: #ffe;
height: 100px;
margin-bottom: 15px;
visibility: visible !important;
}
.ui-sortable-placeholder * { visibility: hidden; }
.ui-plugin { padding-top: 15px; }

View file

@ -0,0 +1,65 @@
var locPlugin = PHPCI.UiPlugin.extend({
id: 'build-lines-chart',
css: 'col-lg-6 col-md-6 col-sm-12 col-xs-12',
title: 'Lines of Code',
lastData: null,
displayOnUpdate: false,
register: function() {
var self = this;
var query = PHPCI.registerQuery('phploc-lines', -1, {num_builds: 10, key: 'phploc'})
$(window).on('phploc-lines', function(data) {
self.onUpdate(data);
});
$(window).on('build-updated', function(data) {
if (data.queryData.status > 1) {
self.displayOnUpdate = true;
query();
}
});
google.load("visualization", "1", {packages:["corechart"]});
},
render: function() {
return $('<div id="phploc-lines"></div>').text('This chart will display once the build has completed.');
},
onUpdate: function(e) {
this.lastData = e.queryData;
if (this.displayOnUpdate) {
this.displayChart();
}
},
displayChart: function() {
var build = this.lastData;
if (!build || !build.length) {
return;
}
$('#phploc-lines').empty().animate({height: '275px'});
var data = [["Build", "Lines", "Comment Lines", "Non-Comment Lines", "Logical Lines"]];
for (var idx in build) {
data.push(['Build ' + build[idx].build_id, parseInt(build[idx].value.LOC), parseInt(build[idx].value.CLOC), parseInt(build[idx].value.NCLOC), parseInt(build[idx].value.LLOC)]);
}
var data = google.visualization.arrayToDataTable(data);
var options = {
hAxis: {title: 'Builds'},
vAxis: {title: 'Lines'},
backgroundColor: { fill: 'transparent' }
};
var chart = new google.visualization.LineChart(document.getElementById('phploc-lines'));
chart.draw(data, options);
}
});
PHPCI.registerPlugin(new locPlugin());

View file

@ -0,0 +1,23 @@
var logPlugin = PHPCI.UiPlugin.extend({
id: 'build-log',
css: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
title: 'Build Log',
init: function(){
this._super();
},
render: function() {
var container = $('<pre></pre>');
container.css({height: '300px', 'overflow-y': 'auto'});
container.html(PHPCI.buildData.log);
return container;
},
onUpdate: function(e) {
$('#build-log pre').html(e.queryData.log);
}
});
PHPCI.registerPlugin(new logPlugin());

View file

@ -0,0 +1,37 @@
var timePlugin = PHPCI.UiPlugin.extend({
id: 'build-time',
css: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
title: null,
box: true,
init: function(){
this._super();
},
render: function() {
return '<table class="table table-striped table-bordered">' +
'<thead>' +
'<tr>' +
'<th style="width: 33.3%">Build Created</th>' +
'<th style="width: 33.3%">Build Started</th>' +
'<th style="width: 33.3%">Build Finished</th>' +
'</tr>' +
'</thead>' +
'<tbody>' +
'<tr>' +
'<td id="created">' + PHPCI.buildData.created + '</td>' +
'<td id="started">' + PHPCI.buildData.started + '</td>' +
'<td id="finished">' + PHPCI.buildData.finished + '</td>' +
'</tr>' +
'</tbody>' +
'</table>';
},
onUpdate: function(build) {
$('#created').text(build.created);
$('#started').text(build.started);
$('#finished').text(build.finished);
}
});
PHPCI.registerPlugin(new timePlugin());

View file

@ -0,0 +1,98 @@
var plugin = PHPCI.UiPlugin.extend({
id: 'build-warnings-chart',
css: 'col-lg-6 col-md-6 col-sm-12 col-xs-12',
title: 'Quality Trend',
data: {},
keys: null,
displayOnUpdate: false,
register: function() {
var self = this;
var query1 = PHPCI.registerQuery('phpmd-warnings', -1, {num_builds: 10, key: 'phpmd-warnings'})
var query2 = PHPCI.registerQuery('phpcs-warnings', -1, {num_builds: 10, key: 'phpcs-warnings'})
var query3 = PHPCI.registerQuery('phpcs-errors', -1, {num_builds: 10, key: 'phpcs-errors'})
$(window).on('phpmd-warnings phpcs-warnings phpcs-errors', function(data) {
self.onUpdate(data);
});
$(window).on('build-updated', function(data) {
if (data.queryData.status > 1) {
self.displayOnUpdate = true;
query1();
query2();
query3();
}
});
google.load("visualization", "1", {packages:["corechart"]});
},
render: function() {
return $('<div id="build-warnings"></div>').text('This chart will display once the build has completed.');
},
onUpdate: function(e) {
var self = this;
var build = e.queryData;
if (!build || !build.length) {
return;
}
for (var i in build) {
var buildId = build[i]['build_id'];
var metaKey = build[i]['key'];
var metaVal = build[i]['value'];
if (!self.data[buildId]) {
self.data[buildId] = {};
}
self.data[buildId][metaKey] = metaVal;
self.keys = Object.keys(self.data[buildId]);
}
if (self.displayOnUpdate) {
self.displayChart();
}
},
displayChart: function() {
var self = this;
$('#build-warnings').empty().animate({height: '275px'});
var titles = ['Build'];
var keys = self.keys;
for (var i in keys) {
var t = {'phpmd-warnings': 'PHPMD Warnings', 'phpcs-warnings': 'PHPCS Warnings', 'phpcs-errors': 'PHPCS Errors'};
titles.push(t[keys[i]]);
}
var data = [titles];
for (var build in self.data) {
var thisBuild = ['#' + build];
for (var i in keys) {
thisBuild.push(parseInt(self.data[build][keys[i]]));
}
data.push(thisBuild);
}
var data = google.visualization.arrayToDataTable(data);
var options = {
hAxis: {title: 'Build'},
vAxis: {title: 'Warnings'},
backgroundColor: { fill: 'transparent' }
};
var chart = new google.visualization.LineChart(document.getElementById('build-warnings'));
chart.draw(data, options);
}
});
PHPCI.registerPlugin(new plugin());

64
public/assets/js/class.js Normal file
View file

@ -0,0 +1,64 @@
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
*/
// Inspired by base2 and Prototype
(function(){
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
this.Class = function(){};
// Create a new Class that inherits from this class
Class.extend = function(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.prototype.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
})();

6
public/assets/js/jqueryui.js vendored Executable file

File diff suppressed because one or more lines are too long

View file

@ -13,95 +13,6 @@ function confirmDelete(url)
}
}
/**
* Updates the build screen. Called at regular intervals on /build/view/X
*/
function updateBuildView(data)
{
$('#status').attr('class', 'alert');
var cls;
var msg;
switch(data.status)
{
case 0:
cls = 'alert-info';
msg = 'This build has not yet started.';
break;
case 1:
cls = 'alert-warning';
msg = 'This build is in progress.';
break;
case 2:
cls = 'alert-success';
msg = 'This build was successful!';
break;
case 3:
cls = 'alert-error';
msg = 'This build has failed.';
break;
}
$('#status').addClass(cls).text(msg);
if(data.created)
{
$('#created').text(data.created);
}
else
{
$('#created').text('Not created yet.');
}
if(data.started)
{
$('#started').text(data.started);
}
else
{
$('#started').text('Not started yet.');
}
if(data.finished)
{
$('#finished').text(data.finished);
}
else
{
$('#finished').text('Not finished yet.');
}
if(data.plugins)
{
$('#plugins').empty();
for(var plugin in data.plugins)
{
var row = $('<tr>').addClass(data.plugins[plugin] ? 'success' : 'error');
var name = $('<td>').html('<strong>' + formatPluginName(plugin) + '</strong>');
var status = $('<td>').text(data.plugins[plugin] ? 'OK' : 'Failed');
row.append(name);
row.append(status);
$('#plugins').append(row);
}
}
else
{
var row = $('<tr>');
var col = $('<td>').attr('colspan', 2).text('No plugins have run yet.');
row.append(col);
$('#plugins').empty().append(row);
}
$('#log').html(data.log);
}
/**
* Used to initialise the project form:
*/
@ -170,17 +81,129 @@ function setupProjectForm()
});
}
var PHPCIObject = Class.extend({
buildId: null,
plugins: {},
observers: {},
buildData: {},
queries: {},
updateInterval: null,
function formatPluginName (name) {
name = name.replace(new RegExp('_', 'g'), ' ');
name = ucwords(name);
name = name.replace(new RegExp('Php', 'g'), 'PHP');
init: function(build) {
this.buildId = build;
this.registerQuery('build-updated', 10);
},
return name;
}
registerQuery: function(name, seconds, query) {
var self = this;
var uri = 'build/meta/' + self.buildId;
var query = query || {};
function ucwords (str) {
return (str + '').replace(/^([a-z\u00E0-\u00FC])|\s+([a-z\u00E0-\u00FC])/g, function ($1) {
return $1.toUpperCase();
});
}
if (name == 'build-updated') {
uri = 'build/data/' + self.buildId;
}
var cb = function() {
$.getJSON(window.PHPCI_URL + uri, query, function(data) {
$(window).trigger({type: name, queryData: data});
});
};
if (seconds != -1) {
setInterval(cb, seconds * 1000);
}
return cb;
},
registerPlugin: function(plugin) {
this.plugins[plugin.id] = plugin;
plugin.register();
},
storePluginOrder: function () {
var renderOrder = [];
$('.ui-plugin > div').each(function() {
renderOrder.push($(this).attr('id'));
});
localStorage.setItem('phpci-plugin-order', JSON.stringify(renderOrder));
},
renderPlugins: function() {
var self = this;
var rendered = [];
var renderOrder = localStorage.getItem('phpci-plugin-order');
if (renderOrder) {
renderOrder = JSON.parse(renderOrder);
} else {
renderOrder = ['build-time', 'build-lines-chart', 'build-warnings-chart', 'build-log'];
}
for (var idx in renderOrder) {
var key = renderOrder[idx];
self.renderPlugin(self.plugins[key]);
rendered.push(key);
}
for (var key in this.plugins) {
if (rendered.indexOf(key) == -1) {
self.renderPlugin(self.plugins[key]);
}
}
$('#plugins').sortable({
handle: '.title',
connectWith: '#plugins',
update: self.storePluginOrder
});
$(window).trigger({type: 'build-updated', queryData: self.buildData});
},
renderPlugin: function(plugin) {
var output = $('<div></div>').addClass('box-content').append(plugin.render());
var container = $('<div></div>').addClass('ui-plugin ' + plugin.css);
var content = $('<div></div>').attr('id', plugin.id).append(output);
if (plugin.box) {
content.addClass('box');
}
if (plugin.title) {
content.prepend('<h3 class="title">'+plugin.title+'</h3>');
}
content.append(output);
container.append(content);
$('#plugins').append(container);
},
UiPlugin: Class.extend({
id: null,
css: 'col-lg-4 col-md-6 col-sm-12 col-xs-12',
box: true,
init: function(){
},
register: function() {
var self = this;
$(window).on('build-updated', function(data) {
self.onUpdate(data);
});
},
render: function () {
return '';
},
onUpdate: function (build) {
}
})
});