diff --git a/docs/en/configuring.md b/docs/en/configuring.md index 62aba1e2..63af0741 100644 --- a/docs/en/configuring.md +++ b/docs/en/configuring.md @@ -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 ``` diff --git a/public/assets/css/main.css b/public/assets/css/main.css index b743f890..4f7fe845 100644 --- a/public/assets/css/main.css +++ b/public/assets/css/main.css @@ -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); } +} diff --git a/public/assets/js/app.js b/public/assets/js/app.js index a3df4a4e..dcf7c630 100644 --- a/public/assets/js/app.js +++ b/public/assets/js/app.js @@ -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(); diff --git a/public/assets/js/dashboard-widgets/all_projects.js b/public/assets/js/dashboard-widgets/all_projects.js new file mode 100644 index 00000000..a13bcf58 --- /dev/null +++ b/public/assets/js/dashboard-widgets/all_projects.js @@ -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(); diff --git a/public/assets/js/dashboard-widgets/build_errors.js b/public/assets/js/dashboard-widgets/build_errors.js new file mode 100644 index 00000000..4b0ba631 --- /dev/null +++ b/public/assets/js/dashboard-widgets/build_errors.js @@ -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(); diff --git a/public/assets/js/dashboard-widgets/last_builds.js b/public/assets/js/dashboard-widgets/last_builds.js new file mode 100644 index 00000000..e452f045 --- /dev/null +++ b/public/assets/js/dashboard-widgets/last_builds.js @@ -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(); diff --git a/src/PHPCensor/Command/InstallCommand.php b/src/PHPCensor/Command/InstallCommand.php index f0e3b3f6..a9c589ea 100644 --- a/src/PHPCensor/Command/InstallCommand.php +++ b/src/PHPCensor/Command/InstallCommand.php @@ -293,6 +293,14 @@ class InstallCommand extends Command ], ], ], + 'dashboard_widgets' => [ + 'all_projects' => [ + 'side' => 'left', + ], + 'last_builds' => [ + 'side' => 'right', + ], + ], ]; } diff --git a/src/PHPCensor/Controller/BuildController.php b/src/PHPCensor/Controller/BuildController.php index dd538db9..5e9a68e4 100644 --- a/src/PHPCensor/Controller/BuildController.php +++ b/src/PHPCensor/Controller/BuildController.php @@ -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; - } } diff --git a/src/PHPCensor/Controller/HomeController.php b/src/PHPCensor/Controller/HomeController.php index 04e1fa5b..3cfbc2dc 100644 --- a/src/PHPCensor/Controller/HomeController.php +++ b/src/PHPCensor/Controller/HomeController.php @@ -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 */ 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; - } } diff --git a/src/PHPCensor/Controller/ProjectController.php b/src/PHPCensor/Controller/ProjectController.php index 7bc5b9d4..91e94182 100644 --- a/src/PHPCensor/Controller/ProjectController.php +++ b/src/PHPCensor/Controller/ProjectController.php @@ -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. */ diff --git a/src/PHPCensor/Controller/WidgetAllProjectsController.php b/src/PHPCensor/Controller/WidgetAllProjectsController.php new file mode 100644 index 00000000..0beb251e --- /dev/null +++ b/src/PHPCensor/Controller/WidgetAllProjectsController.php @@ -0,0 +1,143 @@ +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; + } +} diff --git a/src/PHPCensor/Controller/WidgetBuildErrorsController.php b/src/PHPCensor/Controller/WidgetBuildErrorsController.php new file mode 100644 index 00000000..812475db --- /dev/null +++ b/src/PHPCensor/Controller/WidgetBuildErrorsController.php @@ -0,0 +1,83 @@ +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(); + } +} diff --git a/src/PHPCensor/Controller/WidgetLastBuildsController.php b/src/PHPCensor/Controller/WidgetLastBuildsController.php new file mode 100644 index 00000000..c50598ef --- /dev/null +++ b/src/PHPCensor/Controller/WidgetLastBuildsController.php @@ -0,0 +1,66 @@ +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; + } +} diff --git a/src/PHPCensor/Languages/lang.en.php b/src/PHPCensor/Languages/lang.en.php index b6f3d287..08f3be4d 100644 --- a/src/PHPCensor/Languages/lang.en.php +++ b/src/PHPCensor/Languages/lang.en.php @@ -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', diff --git a/src/PHPCensor/Store/BuildStore.php b/src/PHPCensor/Store/BuildStore.php index b69d9bbb..7b03f00a 100644 --- a/src/PHPCensor/Store/BuildStore.php +++ b/src/PHPCensor/Store/BuildStore.php @@ -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. * diff --git a/src/PHPCensor/Store/ProjectStore.php b/src/PHPCensor/Store/ProjectStore.php index a77eef7a..a7bc49f8 100644 --- a/src/PHPCensor/Store/ProjectStore.php +++ b/src/PHPCensor/Store/ProjectStore.php @@ -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. * diff --git a/src/PHPCensor/View/Home/index.phtml b/src/PHPCensor/View/Home/index.phtml index 873bc86b..234afe15 100644 --- a/src/PHPCensor/View/Home/index.phtml +++ b/src/PHPCensor/View/Home/index.phtml @@ -1,153 +1,29 @@ -
-
- -
-
-

-
- -
+ $params) { ?> +
+
- -
- -
-
- +
- +
-
-
-

+ $params) { ?> +
+
- -
-
    - - - 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; - ?> -
  • - - format('Y-m-d'); ?> - -
  • - - - - -
  • - -
    - - format('H:i:s'); - if ($build->getStatus() != Build::STATUS_PENDING) { - echo ' — ' . $build->getDuration(); ?> - -

    - - getProject()->getTitle(); ?> - - - — - - Build #getId(); ?> - - — - getSourceHumanize()); ?> -

    - -
    - getBranch(); ?> - - getTag()): ?> / - - - - - getCommitId())) { - echo ' — '; - echo sprintf( - '%s %s', - $build->getCommitLink(), - substr($build->getCommitId(), 0, 7), - $build->getCommitterEmail() ? ('(' . $build->getCommitterEmail() . ')') : '' - ); - if (!empty($build->getCommitMessage())) { - echo ' — '; - print $build->getCommitMessage(); - } - } - ?> -
    -
    -
  • - - - - -
  • - -
  • -
-
-
+
- +
diff --git a/src/PHPCensor/View/Home/dashboard-projects.phtml b/src/PHPCensor/View/WidgetAllProjects/index-projects.phtml similarity index 100% rename from src/PHPCensor/View/Home/dashboard-projects.phtml rename to src/PHPCensor/View/WidgetAllProjects/index-projects.phtml diff --git a/src/PHPCensor/View/WidgetAllProjects/index.phtml b/src/PHPCensor/View/WidgetAllProjects/index.phtml new file mode 100644 index 00000000..7e614c90 --- /dev/null +++ b/src/PHPCensor/View/WidgetAllProjects/index.phtml @@ -0,0 +1,15 @@ + +
+
+

+
+ +
+
+
+ +
+
+ diff --git a/src/PHPCensor/View/Home/ajax-dashboard-project.phtml b/src/PHPCensor/View/WidgetAllProjects/update.phtml similarity index 100% rename from src/PHPCensor/View/Home/ajax-dashboard-project.phtml rename to src/PHPCensor/View/WidgetAllProjects/update.phtml diff --git a/src/PHPCensor/View/WidgetBuildErrors/index.phtml b/src/PHPCensor/View/WidgetBuildErrors/index.phtml new file mode 100644 index 00000000..18595f33 --- /dev/null +++ b/src/PHPCensor/View/WidgetBuildErrors/index.phtml @@ -0,0 +1,13 @@ + +
+
+

+
+
+ +
+
diff --git a/src/PHPCensor/View/WidgetBuildErrors/update.phtml b/src/PHPCensor/View/WidgetBuildErrors/update.phtml new file mode 100644 index 00000000..47e46086 --- /dev/null +++ b/src/PHPCensor/View/WidgetBuildErrors/update.phtml @@ -0,0 +1,157 @@ + $project_envs): + if (!isset($projects[$project_id])) { + echo ''; + 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'); + } + } + } + +?> +
+
+ +
+

+ + getTitle(); ?> + + + + +

+ +

+ +

+ +
+
+ +
+ +
+ getAllowPublicStatus()): ?> + + + + +
+ +
+ + '; + } 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 ''; + } + } ?> +
+
+
+ + + diff --git a/src/PHPCensor/View/WidgetLastBuilds/index.phtml b/src/PHPCensor/View/WidgetLastBuilds/index.phtml new file mode 100644 index 00000000..24bc7476 --- /dev/null +++ b/src/PHPCensor/View/WidgetLastBuilds/index.phtml @@ -0,0 +1,13 @@ + +
+
+

+
+
+ +
+
diff --git a/src/PHPCensor/View/Home/ajax-timeline.phtml b/src/PHPCensor/View/WidgetLastBuilds/update.phtml similarity index 100% rename from src/PHPCensor/View/Home/ajax-timeline.phtml rename to src/PHPCensor/View/WidgetLastBuilds/update.phtml