Split dashboard into widgets. Add build errors widget.
This commit is contained in:
parent
257aabc113
commit
11f58d7c2b
|
@ -66,4 +66,23 @@ php-censor:
|
|||
port: 389
|
||||
base_dn: 'dc=php-censor,dc=local'
|
||||
mail_attribute: mail
|
||||
dashboard_widgets:
|
||||
all_projects:
|
||||
side: left
|
||||
last_builds:
|
||||
side: right
|
||||
```
|
||||
|
||||
Dashboard widgets
|
||||
-----------------
|
||||
|
||||
* `all_projects` - all projects build status
|
||||
* `last_builds` - last builds
|
||||
* `build_errors` - not successful builds
|
||||
|
||||
Each widget can be located in the left or right column, use the `side` option for this:
|
||||
|
||||
```yml
|
||||
all_projects:
|
||||
side: left
|
||||
```
|
||||
|
|
|
@ -599,3 +599,24 @@ h6,
|
|||
.timeline > .time-label > span {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.loader {
|
||||
margin: 10px auto;
|
||||
border: 8px solid #f3f3f3;
|
||||
border-radius: 50%;
|
||||
border-top: 8px solid #8AA4AF;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
-webkit-animation: spin 1s linear infinite;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
var PHPCensor = {
|
||||
intervals: {},
|
||||
widgets: {},
|
||||
|
||||
init: function () {
|
||||
$(document).ready(function () {
|
||||
|
@ -11,11 +12,6 @@ var PHPCensor = {
|
|||
if (typeof PROJECT_ID != 'undefined') {
|
||||
PHPCensor.intervals.getProjectBuilds = setInterval(PHPCensor.getProjectBuilds, 10000);
|
||||
}
|
||||
|
||||
if (typeof DASHBOARD != 'undefined') {
|
||||
PHPCensor.intervals.getDashboard = setInterval(PHPCensor.getDashboard, 10000);
|
||||
PHPCensor.intervals.getTimeline = setInterval(PHPCensor.getTimeline, 10000);
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('builds-updated', function (e, data) {
|
||||
|
@ -47,34 +43,6 @@ var PHPCensor = {
|
|||
});
|
||||
},
|
||||
|
||||
getDashboard: function () {
|
||||
$('.project-box').each(function (index) {
|
||||
var projectId = this.id.substring(12);
|
||||
|
||||
$.ajax({
|
||||
url: APP_URL + 'project/ajax-dashboard-project/' + projectId,
|
||||
|
||||
success: function (data) {
|
||||
$(('#project-box-' + projectId)).html(data);
|
||||
},
|
||||
|
||||
error: PHPCensor.handleFailedAjax
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getTimeline: function () {
|
||||
$.ajax({
|
||||
url: APP_URL + 'build/ajax-timeline',
|
||||
|
||||
success: function (data) {
|
||||
$('#timeline-box').html(data);
|
||||
},
|
||||
|
||||
error: PHPCensor.handleFailedAjax
|
||||
});
|
||||
},
|
||||
|
||||
updateHeaderBuilds: function (data) {
|
||||
$('.app-pending-list').empty();
|
||||
$('.app-running-list').empty();
|
||||
|
|
40
public/assets/js/dashboard-widgets/all_projects.js
Normal file
40
public/assets/js/dashboard-widgets/all_projects.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
PHPCensor.widgets.allProjects = {
|
||||
interval: null,
|
||||
|
||||
init: function () {
|
||||
$(document).ready(function () {
|
||||
PHPCensor.widgets.allProjects.load();
|
||||
});
|
||||
},
|
||||
|
||||
load: function() {
|
||||
$.ajax({
|
||||
url: APP_URL + 'widget-all-projects',
|
||||
|
||||
success: function (data) {
|
||||
$(('#widget-all_projects-container')).html(data);
|
||||
PHPCensor.widgets.allProjects.interval = setInterval(PHPCensor.widgets.allProjects.update, 10000);
|
||||
},
|
||||
|
||||
error: PHPCensor.handleFailedAjax
|
||||
});
|
||||
},
|
||||
|
||||
update: function () {
|
||||
$('.project-box').each(function (index) {
|
||||
var projectId = this.id.substring(12);
|
||||
|
||||
$.ajax({
|
||||
url: APP_URL + 'widget-all-projects/update/' + projectId,
|
||||
|
||||
success: function (data) {
|
||||
$(('#project-box-' + projectId)).html(data);
|
||||
},
|
||||
|
||||
error: PHPCensor.handleFailedAjax
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
PHPCensor.widgets.allProjects.init();
|
36
public/assets/js/dashboard-widgets/build_errors.js
Normal file
36
public/assets/js/dashboard-widgets/build_errors.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
PHPCensor.widgets.buildErrors = {
|
||||
interval: null,
|
||||
|
||||
init: function () {
|
||||
$(document).ready(function () {
|
||||
PHPCensor.widgets.buildErrors.load();
|
||||
});
|
||||
},
|
||||
|
||||
load: function() {
|
||||
$.ajax({
|
||||
url: APP_URL + 'widget-build-errors',
|
||||
|
||||
success: function (data) {
|
||||
$(('#widget-build_errors-container')).html(data);
|
||||
PHPCensor.widgets.buildErrors.interval = setInterval(PHPCensor.widgets.buildErrors.update, 10000);
|
||||
},
|
||||
|
||||
error: PHPCensor.handleFailedAjax
|
||||
});
|
||||
},
|
||||
|
||||
update: function () {
|
||||
$.ajax({
|
||||
url: APP_URL + 'widget-build-errors/update',
|
||||
|
||||
success: function (data) {
|
||||
$(('#dashboard-build-errors')).html(data);
|
||||
},
|
||||
|
||||
error: PHPCensor.handleFailedAjax
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
PHPCensor.widgets.buildErrors.init();
|
36
public/assets/js/dashboard-widgets/last_builds.js
Normal file
36
public/assets/js/dashboard-widgets/last_builds.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
PHPCensor.widgets.lastBuilds = {
|
||||
interval: null,
|
||||
|
||||
init: function () {
|
||||
$(document).ready(function () {
|
||||
PHPCensor.widgets.lastBuilds.load();
|
||||
});
|
||||
},
|
||||
|
||||
load: function() {
|
||||
$.ajax({
|
||||
url: APP_URL + 'widget-last-builds',
|
||||
|
||||
success: function (data) {
|
||||
$(('#widget-last_builds-container')).html(data);
|
||||
PHPCensor.widgets.lastBuilds.interval = setInterval(PHPCensor.widgets.lastBuilds.update, 10000);
|
||||
},
|
||||
|
||||
error: PHPCensor.handleFailedAjax
|
||||
});
|
||||
},
|
||||
|
||||
update: function () {
|
||||
$.ajax({
|
||||
url: APP_URL + 'widget-last-builds/update',
|
||||
|
||||
success: function (data) {
|
||||
$('#timeline-box').html(data);
|
||||
},
|
||||
|
||||
error: PHPCensor.handleFailedAjax
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
PHPCensor.widgets.lastBuilds.init();
|
|
@ -293,6 +293,14 @@ class InstallCommand extends Command
|
|||
],
|
||||
],
|
||||
],
|
||||
'dashboard_widgets' => [
|
||||
'all_projects' => [
|
||||
'side' => 'left',
|
||||
],
|
||||
'last_builds' => [
|
||||
'side' => 'right',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -368,20 +368,4 @@ class BuildController extends Controller
|
|||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function ajaxTimeline()
|
||||
{
|
||||
$builds = $this->buildStore->getLatestBuilds(null, 10);
|
||||
foreach ($builds as &$build) {
|
||||
$build = BuildFactory::getBuild($build);
|
||||
}
|
||||
|
||||
$view = new b8\View('Home/ajax-timeline');
|
||||
$view->builds = $builds;
|
||||
|
||||
$this->response->disableLayout();
|
||||
$this->response->setContent($view->render());
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,120 +3,40 @@
|
|||
namespace PHPCensor\Controller;
|
||||
|
||||
use b8;
|
||||
use PHPCensor\BuildFactory;
|
||||
use PHPCensor\Helper\Lang;
|
||||
use PHPCensor\Model\Build;
|
||||
use PHPCensor\Controller;
|
||||
|
||||
/**
|
||||
* Home Controller - Displays the Dashboard.
|
||||
*
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
*/
|
||||
class HomeController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var \PHPCensor\Store\BuildStore
|
||||
*/
|
||||
protected $buildStore;
|
||||
|
||||
/**
|
||||
* @var \PHPCensor\Store\ProjectStore
|
||||
*/
|
||||
protected $projectStore;
|
||||
|
||||
/**
|
||||
* @var \PHPCensor\Store\ProjectGroupStore
|
||||
*/
|
||||
protected $groupStore;
|
||||
|
||||
/**
|
||||
* Initialise the controller, set up stores and services.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->buildStore = b8\Store\Factory::getStore('Build');
|
||||
$this->projectStore = b8\Store\Factory::getStore('Project');
|
||||
$this->groupStore = b8\Store\Factory::getStore('ProjectGroup');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display dashboard:
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->layout->title = Lang::get('dashboard');
|
||||
$builds = $this->buildStore->getLatestBuilds(null, 10);
|
||||
|
||||
foreach ($builds as &$build) {
|
||||
$build = BuildFactory::getBuild($build);
|
||||
$widgets = [
|
||||
'left' => [],
|
||||
'right' => [],
|
||||
];
|
||||
$widgets_config = b8\Config::getInstance()->get('php-censor.dashboard_widgets', [
|
||||
'all_projects' => [
|
||||
'side' => 'left',
|
||||
],
|
||||
'last_builds' => [
|
||||
'side' => 'right',
|
||||
],
|
||||
]);
|
||||
foreach($widgets_config as $name => $params) {
|
||||
$side = (isset($params['side']) and ($params['side'] == 'right')) ? 'right' : 'left';
|
||||
$widgets[$side][$name] = $params;
|
||||
}
|
||||
|
||||
$this->view->builds = $builds;
|
||||
$this->view->groups = $this->getGroupInfo();
|
||||
$this->view->widgets = $widgets;
|
||||
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the HTML for the project overview section of the dashboard.
|
||||
* @param $projects
|
||||
* @return string
|
||||
*/
|
||||
protected function getSummaryHtml($projects)
|
||||
{
|
||||
$summaryBuilds = [];
|
||||
$successes = [];
|
||||
$failures = [];
|
||||
$counts = [];
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$summaryBuilds[$project->getId()] = $this->buildStore->getLatestBuilds($project->getId());
|
||||
|
||||
$count = $this->buildStore->getWhere(
|
||||
['project_id' => $project->getId()],
|
||||
1,
|
||||
0,
|
||||
[],
|
||||
['id' => 'DESC']
|
||||
);
|
||||
$counts[$project->getId()] = $count['count'];
|
||||
|
||||
$success = $this->buildStore->getLastBuildByStatus($project->getId(), Build::STATUS_SUCCESS);
|
||||
$failure = $this->buildStore->getLastBuildByStatus($project->getId(), Build::STATUS_FAILED);
|
||||
|
||||
$successes[$project->getId()] = $success;
|
||||
$failures[$project->getId()] = $failure;
|
||||
}
|
||||
|
||||
$view = new b8\View('Home/dashboard-projects');
|
||||
$view->projects = $projects;
|
||||
$view->builds = $summaryBuilds;
|
||||
$view->successful = $successes;
|
||||
$view->failed = $failures;
|
||||
$view->counts = $counts;
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of the project groups we have, and what projects they have in them.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getGroupInfo()
|
||||
{
|
||||
$rtn = [];
|
||||
$groups = $this->groupStore->getWhere([], 100, 0, [], ['title' => 'ASC']);
|
||||
|
||||
foreach ($groups['items'] as $group) {
|
||||
$thisGroup = ['title' => $group->getTitle()];
|
||||
$projects = $this->projectStore->getByGroupId($group->getId(), false);
|
||||
$thisGroup['projects'] = $projects['items'];
|
||||
$thisGroup['summary'] = $this->getSummaryHtml($thisGroup['projects']);
|
||||
$rtn[] = $thisGroup;
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -287,35 +287,6 @@ class ProjectController extends PHPCensor\Controller
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Render latest builds for project as HTML table.
|
||||
*
|
||||
* @param int $projectId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDashboardProjectHtml($projectId)
|
||||
{
|
||||
$count = $this->buildStore->getWhere(
|
||||
['project_id' => $projectId],
|
||||
1,
|
||||
0,
|
||||
[],
|
||||
['id' => 'DESC']
|
||||
);
|
||||
$counts = $count['count'];
|
||||
|
||||
$view = new b8\View('Home/ajax-dashboard-project');
|
||||
|
||||
$view->project = $this->projectStore->getById($projectId);
|
||||
$view->builds = $this->buildStore->getLatestBuilds($projectId);
|
||||
$view->successful = $this->buildStore->getLastBuildByStatus($projectId, PHPCensor\Model\Build::STATUS_SUCCESS);
|
||||
$view->failed = $this->buildStore->getLastBuildByStatus($projectId, PHPCensor\Model\Build::STATUS_FAILED);
|
||||
$view->counts = $counts;
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new project. Handles both the form, and processing.
|
||||
*/
|
||||
|
@ -605,21 +576,6 @@ class ProjectController extends PHPCensor\Controller
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $projectId
|
||||
*
|
||||
* @return b8\Http\Response
|
||||
*/
|
||||
public function ajaxDashboardProject($projectId)
|
||||
{
|
||||
$builds = $this->getDashboardProjectHtml($projectId);
|
||||
|
||||
$this->response->disableLayout();
|
||||
$this->response->setContent($builds);
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of repositories from Github's API.
|
||||
*/
|
||||
|
|
143
src/PHPCensor/Controller/WidgetAllProjectsController.php
Normal file
143
src/PHPCensor/Controller/WidgetAllProjectsController.php
Normal file
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
namespace PHPCensor\Controller;
|
||||
|
||||
use b8;
|
||||
use PHPCensor\BuildFactory;
|
||||
use PHPCensor\Helper\Lang;
|
||||
use PHPCensor\Model\Build;
|
||||
use PHPCensor\Controller;
|
||||
|
||||
/**
|
||||
* Widget All Projects Controller
|
||||
*/
|
||||
class WidgetAllProjectsController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var \PHPCensor\Store\BuildStore
|
||||
*/
|
||||
protected $buildStore;
|
||||
|
||||
/**
|
||||
* @var \PHPCensor\Store\ProjectStore
|
||||
*/
|
||||
protected $projectStore;
|
||||
|
||||
/**
|
||||
* @var \PHPCensor\Store\ProjectGroupStore
|
||||
*/
|
||||
protected $groupStore;
|
||||
|
||||
/**
|
||||
* Initialise the controller, set up stores and services.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->buildStore = b8\Store\Factory::getStore('Build');
|
||||
$this->projectStore = b8\Store\Factory::getStore('Project');
|
||||
$this->groupStore = b8\Store\Factory::getStore('ProjectGroup');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display dashboard:
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->view->groups = $this->getGroupInfo();
|
||||
|
||||
$this->response->disableLayout();
|
||||
$this->response->setContent($this->view->render());
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the HTML for the project overview section of the dashboard.
|
||||
* @param $projects
|
||||
* @return string
|
||||
*/
|
||||
protected function getSummaryHtml($projects)
|
||||
{
|
||||
$summaryBuilds = [];
|
||||
$successes = [];
|
||||
$failures = [];
|
||||
$counts = [];
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$summaryBuilds[$project->getId()] = $this->buildStore->getLatestBuilds($project->getId());
|
||||
|
||||
$count = $this->buildStore->getWhere(
|
||||
['project_id' => $project->getId()],
|
||||
1,
|
||||
0,
|
||||
[],
|
||||
['id' => 'DESC']
|
||||
);
|
||||
$counts[$project->getId()] = $count['count'];
|
||||
|
||||
$success = $this->buildStore->getLastBuildByStatus($project->getId(), Build::STATUS_SUCCESS);
|
||||
$failure = $this->buildStore->getLastBuildByStatus($project->getId(), Build::STATUS_FAILED);
|
||||
|
||||
$successes[$project->getId()] = $success;
|
||||
$failures[$project->getId()] = $failure;
|
||||
}
|
||||
|
||||
$view = new b8\View('WidgetAllProjects/index-projects');
|
||||
$view->projects = $projects;
|
||||
$view->builds = $summaryBuilds;
|
||||
$view->successful = $successes;
|
||||
$view->failed = $failures;
|
||||
$view->counts = $counts;
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of the project groups we have, and what projects they have in them.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getGroupInfo()
|
||||
{
|
||||
$rtn = [];
|
||||
$groups = $this->groupStore->getWhere([], 100, 0, [], ['title' => 'ASC']);
|
||||
|
||||
foreach ($groups['items'] as $group) {
|
||||
$thisGroup = ['title' => $group->getTitle()];
|
||||
$projects = $this->projectStore->getByGroupId($group->getId(), false);
|
||||
$thisGroup['projects'] = $projects['items'];
|
||||
$thisGroup['summary'] = $this->getSummaryHtml($thisGroup['projects']);
|
||||
$rtn[] = $thisGroup;
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $projectId
|
||||
*
|
||||
* @return b8\Http\Response
|
||||
*/
|
||||
public function update($projectId)
|
||||
{
|
||||
$count = $this->buildStore->getWhere(
|
||||
['project_id' => $projectId],
|
||||
1,
|
||||
0,
|
||||
[],
|
||||
['id' => 'DESC']
|
||||
);
|
||||
$counts = $count['count'];
|
||||
|
||||
$this->view->project = $this->projectStore->getById($projectId);
|
||||
$this->view->builds = $this->buildStore->getLatestBuilds($projectId);
|
||||
$this->view->successful = $this->buildStore->getLastBuildByStatus($projectId, Build::STATUS_SUCCESS);
|
||||
$this->view->failed = $this->buildStore->getLastBuildByStatus($projectId, Build::STATUS_FAILED);
|
||||
$this->view->counts = $counts;
|
||||
|
||||
$this->response->disableLayout();
|
||||
$this->response->setContent($this->view->render());
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
}
|
83
src/PHPCensor/Controller/WidgetBuildErrorsController.php
Normal file
83
src/PHPCensor/Controller/WidgetBuildErrorsController.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace PHPCensor\Controller;
|
||||
|
||||
use b8;
|
||||
use PHPCensor\BuildFactory;
|
||||
use PHPCensor\Helper\Lang;
|
||||
use PHPCensor\Model\Build;
|
||||
use PHPCensor\Controller;
|
||||
|
||||
/**
|
||||
* Widget Build Errors Controller
|
||||
*/
|
||||
class WidgetBuildErrorsController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var \PHPCensor\Store\BuildStore
|
||||
*/
|
||||
protected $buildStore;
|
||||
|
||||
/**
|
||||
* @var \PHPCensor\Store\ProjectStore
|
||||
*/
|
||||
protected $projectStore;
|
||||
|
||||
/**
|
||||
* Initialise the controller, set up stores and services.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->buildStore = b8\Store\Factory::getStore('Build');
|
||||
$this->projectStore = b8\Store\Factory::getStore('Project');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display dashboard:
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$view = new b8\View('WidgetBuildErrors/update');
|
||||
$this->view->projects = $this->renderAllProjectsLatestBuilds($view);
|
||||
|
||||
$this->response->disableLayout();
|
||||
$this->response->setContent($this->view->render());
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return b8\Http\Response
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$this->response->disableLayout();
|
||||
$this->response->setContent($this->renderAllProjectsLatestBuilds($this->view));
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param b8\View $view
|
||||
* @return string
|
||||
*/
|
||||
protected function renderAllProjectsLatestBuilds($view)
|
||||
{
|
||||
$builds = $this->buildStore->getAllProjectsLatestBuilds();
|
||||
|
||||
$view->builds = $builds['projects'];
|
||||
$projects = $this->projectStore->getByIds(array_keys($builds['projects']));
|
||||
|
||||
$view_projects = [];
|
||||
foreach($projects as $id => $project) {
|
||||
if (!$project->getArchived()) {
|
||||
$view_projects[$id] = $project;
|
||||
} else {
|
||||
unset($builds['projects'][$id]);
|
||||
}
|
||||
}
|
||||
$view->projects = $view_projects;
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
}
|
66
src/PHPCensor/Controller/WidgetLastBuildsController.php
Normal file
66
src/PHPCensor/Controller/WidgetLastBuildsController.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace PHPCensor\Controller;
|
||||
|
||||
use b8;
|
||||
use PHPCensor\BuildFactory;
|
||||
use PHPCensor\Helper\Lang;
|
||||
use PHPCensor\Model\Build;
|
||||
use PHPCensor\Controller;
|
||||
|
||||
/**
|
||||
* Widget Last Builds Controller
|
||||
*/
|
||||
class WidgetLastBuildsController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var \PHPCensor\Store\BuildStore
|
||||
*/
|
||||
protected $buildStore;
|
||||
|
||||
/**
|
||||
* Initialise the controller, set up stores and services.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->buildStore = b8\Store\Factory::getStore('Build');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display dashboard:
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$builds = $this->buildStore->getLatestBuilds(null, 10);
|
||||
|
||||
foreach ($builds as &$build) {
|
||||
$build = BuildFactory::getBuild($build);
|
||||
}
|
||||
|
||||
$view = new b8\View('WidgetLastBuilds/update');
|
||||
$view->builds = $builds;
|
||||
|
||||
$this->view->timeline = $view->render();
|
||||
|
||||
$this->response->disableLayout();
|
||||
$this->response->setContent($this->view->render());
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
$builds = $this->buildStore->getLatestBuilds(null, 10);
|
||||
|
||||
foreach ($builds as &$build) {
|
||||
$build = BuildFactory::getBuild($build);
|
||||
}
|
||||
|
||||
$this->view->builds = $builds;
|
||||
|
||||
$this->response->disableLayout();
|
||||
$this->response->setContent($this->view->render());
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
}
|
|
@ -77,6 +77,7 @@ PHP Censor',
|
|||
'last_failed_build' => ' The last failed build was %s.',
|
||||
'never_failed_build' => ' This project has never failed a build.',
|
||||
'view_project' => 'View Project',
|
||||
'projects_with_build_errors' => 'Build errors',
|
||||
|
||||
// Timeline:
|
||||
'latest_builds' => 'Latest Builds',
|
||||
|
|
|
@ -224,6 +224,92 @@ class BuildStore extends Store
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of the latest builds for all projects.
|
||||
* @param int $limit_by_project
|
||||
* @param int $limit_all
|
||||
* @return array
|
||||
*/
|
||||
public function getAllProjectsLatestBuilds($limit_by_project = 5, $limit_all = 10)
|
||||
{
|
||||
// dont fetch log field - contain many data
|
||||
$query = '
|
||||
SELECT
|
||||
{{id}},
|
||||
{{project_id}},
|
||||
{{commit_id}},
|
||||
{{status}},
|
||||
{{branch}},
|
||||
{{create_date}},
|
||||
{{start_date}},
|
||||
{{finish_date}},
|
||||
{{committer_email}},
|
||||
{{commit_message}},
|
||||
{{extra}},
|
||||
{{environment}},
|
||||
{{tag}}
|
||||
FROM {{build}}
|
||||
ORDER BY {{id}} DESC
|
||||
LIMIT 10000
|
||||
';
|
||||
|
||||
$stmt = Database::getConnection('read')->prepareCommon($query);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$projects = [];
|
||||
$latest = [];
|
||||
foreach($res as $item) {
|
||||
$project_id = $item['project_id'];
|
||||
$environment = $item['environment'];
|
||||
if (empty($projects[$project_id])) {
|
||||
$projects[$project_id] = [];
|
||||
}
|
||||
if (empty($projects[$project_id][$environment])) {
|
||||
$projects[$project_id][$environment] = [
|
||||
'latest' => [],
|
||||
'success' => null,
|
||||
'failed' => null,
|
||||
];
|
||||
}
|
||||
$build = null;
|
||||
if (count($projects[$project_id][$environment]['latest']) < $limit_by_project) {
|
||||
$build = new Build($item);
|
||||
$projects[$project_id][$environment]['latest'][] = $build;
|
||||
}
|
||||
if (count($latest) < $limit_all) {
|
||||
if (is_null($build)) {
|
||||
$build = new Build($item);
|
||||
}
|
||||
$latest[] = $build;
|
||||
}
|
||||
if (empty($projects[$project_id][$environment]['success']) and ($item['status'] == Build::STATUS_SUCCESS)) {
|
||||
if (is_null($build)) {
|
||||
$build = new Build($item);
|
||||
}
|
||||
$projects[$project_id][$environment]['success'] = $build;
|
||||
}
|
||||
if (empty($projects[$project_id][$environment]['failed']) and ($item['status'] == Build::STATUS_FAILED)) {
|
||||
if (is_null($build)) {
|
||||
$build = new Build($item);
|
||||
}
|
||||
$projects[$project_id][$environment]['failed'] = $build;
|
||||
}
|
||||
}
|
||||
foreach($projects as $idx => $project) {
|
||||
$projects[$idx] = array_filter($project, function($val) {
|
||||
return ($val['latest'][0]->getStatus() != Build::STATUS_SUCCESS);
|
||||
});
|
||||
}
|
||||
$projects = array_filter($projects);
|
||||
|
||||
return ['projects' => $projects, 'latest' => $latest];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of builds for a given project and commit ID.
|
||||
*
|
||||
|
|
|
@ -58,6 +58,30 @@ class ProjectStore extends Store
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single Project by Ids.
|
||||
* @param int[]
|
||||
* @return Project[]
|
||||
*/
|
||||
public function getByIds($values, $useConnection = 'read')
|
||||
{
|
||||
if (empty($values)) {
|
||||
throw new HttpException('Values passed to ' . __FUNCTION__ . ' cannot be empty.');
|
||||
}
|
||||
|
||||
$query = 'SELECT * FROM {{project}} WHERE {{id}} IN ('.implode(', ', array_map('intval', $values)).')';
|
||||
$stmt = Database::getConnection($useConnection)->prepareCommon($query);
|
||||
|
||||
$rtn = [];
|
||||
if ($stmt->execute()) {
|
||||
while ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$rtn[$data['id']] = new Project($data);
|
||||
}
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple Project by Title.
|
||||
*
|
||||
|
|
|
@ -1,153 +1,29 @@
|
|||
<?php
|
||||
|
||||
use PHPCensor\Helper\Lang;
|
||||
use PHPCensor\Model\Build;
|
||||
|
||||
/**
|
||||
* @var Build[] $builds
|
||||
* @var array $widgets
|
||||
*/
|
||||
|
||||
$all_widgets = array_merge(array_keys($widgets['left']), array_keys($widgets['right']));
|
||||
foreach($all_widgets as $widget) {
|
||||
?><script src="<?= APP_URL ?>assets/js/dashboard-widgets/<?= $widget ?>.js" type="text/javascript"></script><?php
|
||||
}
|
||||
?>
|
||||
<script>
|
||||
var DASHBOARD = true;
|
||||
</script>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-sm-5">
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title"><?php print $group['title']; ?></h3>
|
||||
<div class="box-tools pull-right">
|
||||
<button type="button" class="btn btn-box-tool" data-widget="collapse" data-toggle="tooltip" title="Collapse">
|
||||
<i class="fa fa-minus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<?php foreach ($widgets['left'] as $widget => $params) { ?>
|
||||
<div id="widget-<?= $widget ?>-container">
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
|
||||
<div class="box-body">
|
||||
<?php print $group['summary']; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($widgets['right'])) { ?>
|
||||
<div class="col-sm-7 pull-left">
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title"><?php Lang::out('latest_builds'); ?></h3>
|
||||
<?php foreach ($widgets['right'] as $widget => $params) { ?>
|
||||
<div id="widget-<?= $widget ?>-container">
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
|
||||
<div class="box-body" id="timeline-box">
|
||||
<ul class="timeline">
|
||||
<?php $last = new \DateTime('-1 Year'); ?>
|
||||
|
||||
<?php
|
||||
foreach ($builds as $build):
|
||||
$environment = $build->getEnvironment();
|
||||
$branches = $build->getExtra('branches');
|
||||
|
||||
switch ($build->getStatus()) {
|
||||
case Build::STATUS_PENDING:
|
||||
$updated = $build->getCreateDate();
|
||||
$label = Lang::get('pending');
|
||||
$color = 'blue';
|
||||
break;
|
||||
|
||||
case Build::STATUS_RUNNING:
|
||||
$updated = $build->getStartDate();
|
||||
$label = Lang::get('running');
|
||||
$color = 'yellow';
|
||||
break;
|
||||
|
||||
case Build::STATUS_SUCCESS:
|
||||
$updated = $build->getFinishDate();
|
||||
$label = Lang::get('success');
|
||||
$color = 'green';
|
||||
break;
|
||||
|
||||
case Build::STATUS_FAILED:
|
||||
$updated = $build->getFinishDate();
|
||||
$label = Lang::get('failed');
|
||||
$color = 'red';
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$updated) {
|
||||
$updated = $build->getCreateDate();
|
||||
}
|
||||
|
||||
if ($updated->format('Y-m-d') != $last->format('Y-m-d')): $last = $updated;
|
||||
?>
|
||||
<li class="time-label">
|
||||
<span class="bg-gray">
|
||||
<?= $last->format('Y-m-d'); ?>
|
||||
</span>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- /.timeline-label -->
|
||||
<!-- timeline item -->
|
||||
<li>
|
||||
<i class="fa fa-<?php print $build->getProject()->getIcon(); ?> bg-<?php print $color; ?>"></i>
|
||||
<div class="timeline-item">
|
||||
<span class="time"><i class="fa fa-clock-o"></i>
|
||||
<?php
|
||||
echo $updated->format('H:i:s');
|
||||
if ($build->getStatus() != Build::STATUS_PENDING) {
|
||||
echo ' — ' . $build->getDuration(); ?> <?= Lang::get('seconds');
|
||||
}
|
||||
?>
|
||||
</span>
|
||||
<h3 class="timeline-header">
|
||||
<a href="<?= APP_URL; ?>project/view/<?= $build->getProjectId(); ?>">
|
||||
<?= $build->getProject()->getTitle(); ?>
|
||||
</a>
|
||||
<span><?= $environment; ?></span>
|
||||
—
|
||||
<a href="<?= APP_URL; ?>build/view/<?= $build->getId(); ?>">
|
||||
Build #<?= $build->getId(); ?>
|
||||
</a>
|
||||
—
|
||||
<?php Lang::out($build->getSourceHumanize()); ?>
|
||||
</h3>
|
||||
|
||||
<div class="timeline-body">
|
||||
<a href="<?= $build->getBranchLink();?>"><i class="fa fa-code-fork"></i> <?php echo $build->getBranch(); ?></a>
|
||||
<?= $branches ? ' + '.implode(', ', $branches) : ''; ?>
|
||||
<?php if ($tag = $build->getTag()): ?> /
|
||||
<a href="<?= $build->getTagLink(); ?>" target="_blank">
|
||||
<i class="fa fa-tag"></i> <?= $tag; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
if (!empty($build->getCommitId())) {
|
||||
echo ' — ';
|
||||
echo sprintf(
|
||||
'<a href="%s" target="_blank">%s %s</a>',
|
||||
$build->getCommitLink(),
|
||||
substr($build->getCommitId(), 0, 7),
|
||||
$build->getCommitterEmail() ? ('(' . $build->getCommitterEmail() . ')') : ''
|
||||
);
|
||||
if (!empty($build->getCommitMessage())) {
|
||||
echo ' — ';
|
||||
print $build->getCommitMessage();
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<!-- END timeline item -->
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
||||
<li>
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
|
15
src/PHPCensor/View/WidgetAllProjects/index.phtml
Normal file
15
src/PHPCensor/View/WidgetAllProjects/index.phtml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php foreach ($groups as $group): ?>
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title"><?php print $group['title']; ?></h3>
|
||||
<div class="box-tools pull-right">
|
||||
<button type="button" class="btn btn-box-tool" data-widget="collapse" data-toggle="tooltip" title="Collapse">
|
||||
<i class="fa fa-minus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<?php print $group['summary']; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
13
src/PHPCensor/View/WidgetBuildErrors/index.phtml
Normal file
13
src/PHPCensor/View/WidgetBuildErrors/index.phtml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
use PHPCensor\Helper\Lang;
|
||||
|
||||
?>
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title"><?= Lang::out('projects_with_build_errors') ?></h3>
|
||||
</div>
|
||||
<div class="box-body" id="dashboard-build-errors">
|
||||
<?= $projects ?>
|
||||
</div>
|
||||
</div>
|
157
src/PHPCensor/View/WidgetBuildErrors/update.phtml
Normal file
157
src/PHPCensor/View/WidgetBuildErrors/update.phtml
Normal file
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
use PHPCensor\Helper\Lang;
|
||||
use PHPCensor\Model\Build;
|
||||
|
||||
/**
|
||||
* @var Build[] $builds
|
||||
*/
|
||||
|
||||
foreach($builds as $project_id => $project_envs):
|
||||
if (!isset($projects[$project_id])) {
|
||||
echo '<!-- project '.$project_id.' not set -->';
|
||||
continue;
|
||||
}
|
||||
$project = $projects[$project_id];
|
||||
foreach($project_envs as $environment => $project_env):
|
||||
$statuses = [];
|
||||
$failures = 0;
|
||||
$subcls = 'gray';
|
||||
$cls = '';
|
||||
$success = null;
|
||||
$failure = null;
|
||||
|
||||
// Get the most recent build status to determine the main block colour.
|
||||
$last_build = $project_env['latest'][0];
|
||||
$status = $last_build->getStatus();
|
||||
switch($status) {
|
||||
case 0:
|
||||
$subcls = 'blue';
|
||||
break;
|
||||
case 1:
|
||||
$subcls = 'yellow';
|
||||
break;
|
||||
case 2:
|
||||
$subcls = 'green';
|
||||
break;
|
||||
case 3:
|
||||
$subcls = 'red';
|
||||
break;
|
||||
}
|
||||
// Use the last 5 builds to determine project health:
|
||||
$failures = 0;
|
||||
|
||||
foreach ($project_env['latest'] as $build) {
|
||||
switch ($build->getStatus()) {
|
||||
case 0:
|
||||
$statuses[] = 'pending';
|
||||
break;
|
||||
case 1:
|
||||
$statuses[] = 'running';
|
||||
break;
|
||||
case 2:
|
||||
$statuses[] = 'ok';
|
||||
$success = is_null($success) && !is_null($build->getFinishDate()) ? $build->getFinishDate()->format('Y-m-d H:i:s') : $success;
|
||||
break;
|
||||
case 3:
|
||||
$failures++;
|
||||
$statuses[] = 'failed';
|
||||
$failure = is_null($failure) && !is_null($build->getFinishDate()) ? $build->getFinishDate()->format('Y-m-d H:i:s') : $failure;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$buildCount = count($project_env['latest']);
|
||||
$lastSuccess = $project_env['success'];
|
||||
$lastFailure = $project_env['failed'];
|
||||
$message = Lang::get('no_builds_yet');
|
||||
$shortMessage = Lang::get('no_builds_yet');
|
||||
|
||||
if ($buildCount > 0) {
|
||||
if ($failures > 0) {
|
||||
$shortMessage = Lang::get('x_of_x_failed_short', $failures, $buildCount);
|
||||
$message = Lang::get('x_of_x_failed', $failures, $buildCount);
|
||||
|
||||
if (!is_null($lastSuccess) && !is_null($lastSuccess->getFinishDate())) {
|
||||
$message .= Lang::get('last_successful_build', $lastSuccess->getFinishDate()->format('Y-m-d H:i:s'));
|
||||
} else {
|
||||
$message .= Lang::get('never_built_successfully');
|
||||
}
|
||||
} else {
|
||||
$message = Lang::get('all_builds_passed', $buildCount);
|
||||
$shortMessage = Lang::get('all_builds_passed_short', $buildCount, $buildCount);
|
||||
|
||||
if (!is_null($lastFailure) && !is_null($lastFailure->getFinishDate())) {
|
||||
$message .= Lang::get('last_failed_build', $lastFailure->getFinishDate()->format('Y-m-d H:i:s'));
|
||||
} else {
|
||||
$message .= Lang::get('never_failed_build');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="project-box">
|
||||
<div class="small-box small-box-full bg-<?= $subcls; ?>">
|
||||
|
||||
<div class="inner">
|
||||
<h3>
|
||||
<a href="<?= APP_URL; ?>project/view/<?= $project->getId(); ?>">
|
||||
<?= $project->getTitle(); ?>
|
||||
</a>
|
||||
<?php if (!empty($environment)) { ?>
|
||||
<sup title="<?php Lang::out('environment'); ?>"><?= $environment ?></sup>
|
||||
<?php } ?>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
<?= $message; ?>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fa fa-<?php print $project->getIcon(); ?>"></i>
|
||||
</div>
|
||||
<a href="<?= APP_URL; ?>project/view/<?= $project->getId(); ?>" class="small-box-footer small-box-footer-project">
|
||||
<div class="pull-left" style="margin-left: 10px">
|
||||
<?php if ($project->getAllowPublicStatus()): ?>
|
||||
<i class="fa fa-unlock"></i>
|
||||
<?php else: ?>
|
||||
<i class="fa fa-lock"></i>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php Lang::out('view_project'); ?> <i class="fa fa-arrow-circle-right"></i>
|
||||
</a>
|
||||
|
||||
<?php for ($idx=0; $idx < 5; $idx++) {
|
||||
if (empty($project_env['latest'][$idx])) {
|
||||
echo '<span class="small-box-footer-build small-box-footer bg-gray"><i class="fa fa-minus"></i></span>';
|
||||
} else {
|
||||
$build = $project_env['latest'][$idx];
|
||||
$link = APP_URL . 'build/view/' . $build->id;
|
||||
switch ($build->getStatus()) {
|
||||
case 0:
|
||||
$class = 'bg-blue';
|
||||
$icon = 'fa-clock-o';
|
||||
break;
|
||||
case 1:
|
||||
$class = 'bg-yellow';
|
||||
$icon = 'fa-cogs';
|
||||
break;
|
||||
case 2:
|
||||
$class = 'bg-green';
|
||||
$icon = 'fa-check';
|
||||
break;
|
||||
case 3:
|
||||
$class = 'bg-red';
|
||||
$icon = 'fa-times';
|
||||
break;
|
||||
}
|
||||
echo '<a href="' . $link .'" class="small-box-footer-build small-box-footer ' . $class . '"><i class="fa ' . $icon . '"></i></a>';
|
||||
}
|
||||
} ?>
|
||||
<div style="clear: both;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php endforeach; ?>
|
||||
<?php endforeach; ?>
|
13
src/PHPCensor/View/WidgetLastBuilds/index.phtml
Normal file
13
src/PHPCensor/View/WidgetLastBuilds/index.phtml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
use PHPCensor\Helper\Lang;
|
||||
|
||||
?>
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
<h3 class="box-title"><?php Lang::out('latest_builds'); ?></h3>
|
||||
</div>
|
||||
<div class="box-body" id="timeline-box">
|
||||
<?= $timeline ?>
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in a new issue