diff --git a/public/assets/js/app.js b/public/assets/js/app.js index 6755ff40..07b820d8 100644 --- a/public/assets/js/app.js +++ b/public/assets/js/app.js @@ -38,7 +38,7 @@ var PHPCensor = { getProjectBuilds: function () { $.ajax({ - url: APP_URL + 'project/ajax-builds/' + PROJECT_ID + '?branch=' + PROJECT_BRANCH + '&per_page=' + PER_PAGE, + url: APP_URL + 'project/ajax-builds/' + PROJECT_ID + '?branch=' + PROJECT_BRANCH + '&environment=' + PROJECT_ENVIRONMENT + '&per_page=' + PER_PAGE, success: function (data) { $('#latest-builds').html(data); diff --git a/src/PHPCensor/Command/CreateBuildCommand.php b/src/PHPCensor/Command/CreateBuildCommand.php index 7c15c759..ef9831e8 100644 --- a/src/PHPCensor/Command/CreateBuildCommand.php +++ b/src/PHPCensor/Command/CreateBuildCommand.php @@ -60,6 +60,7 @@ class CreateBuildCommand extends Command $projectId = $input->getArgument('projectId'); $commitId = $input->getOption('commit'); $branch = $input->getOption('branch'); + $environment = $input->getOption('environment'); $project = $this->projectStore->getById($projectId); if (empty($project) || $project->getArchived()) { @@ -67,7 +68,7 @@ class CreateBuildCommand extends Command } try { - $this->buildService->createBuild($project, $commitId, $branch); + $this->buildService->createBuild($project, $environment, $commitId, $branch); $output->writeln('Build Created'); } catch (\Exception $e) { $output->writeln('Failed'); diff --git a/src/PHPCensor/Controller/ProjectController.php b/src/PHPCensor/Controller/ProjectController.php index 8565660d..8407475c 100644 --- a/src/PHPCensor/Controller/ProjectController.php +++ b/src/PHPCensor/Controller/ProjectController.php @@ -58,6 +58,7 @@ class ProjectController extends PHPCensor\Controller public function view($projectId) { $branch = $this->getParam('branch', ''); + $environment = $this->getParam('environment', ''); $project = $this->projectStore->getById($projectId); if (empty($project)) { @@ -66,7 +67,7 @@ class ProjectController extends PHPCensor\Controller $perPage = $_SESSION['php-censor-user']->getFinalPerPage(); $page = $this->getParam('p', 1); - $builds = $this->getLatestBuildsHtml($projectId, urldecode($branch), (($page - 1) * $perPage), $perPage); + $builds = $this->getLatestBuildsHtml($projectId, urldecode($environment), urlencode($branch), (($page - 1) * $perPage), $perPage); $pages = $builds[1] == 0 ? 1 : ceil($builds[1] / $perPage); if ($page > $pages) { @@ -80,12 +81,18 @@ class ProjectController extends PHPCensor\Controller $this->view->project = $project; $this->view->branch = urldecode($branch); $this->view->branches = $this->projectStore->getKnownBranches($projectId); + $this->view->environment = urldecode($environment); + $this->view->environments = $project->getEnvironmentsNames(); $this->view->page = $page; $this->view->pages = $pages; $this->view->perPage = $perPage; $this->layout->title = $project->getTitle(); - $this->layout->subtitle = $this->view->branch; + if (!empty($this->view->environment)) { + $this->layout->subtitle = $this->view->environment; + } else { + $this->layout->subtitle = $this->view->branch; + } return $this->view->render(); } @@ -93,11 +100,22 @@ class ProjectController extends PHPCensor\Controller /** * Create a new pending build for a project. */ - public function build($projectId, $branch = '') + public function build($projectId, $type = null, $id = null) { /* @var \PHPCensor\Model\Project $project */ $project = $this->projectStore->getById($projectId); + $environment = null; + $branch = null; + switch($type) { + case 'environment': + $environment = $id; + break; + case 'branch': + $branch = $id; + break; + } + if (empty($branch)) { $branch = $project->getBranch(); } @@ -115,7 +133,7 @@ class ProjectController extends PHPCensor\Controller } $email = $_SESSION['php-censor-user']->getEmail(); - $build = $this->buildService->createBuild($project, null, urldecode($branch), $email, null, $extra); + $build = $this->buildService->createBuild($project, $environment, null, urldecode($branch), $email, null, $extra); if ($this->buildService->queueError) { $_SESSION['global_error'] = Lang::get('add_to_queue_failed'); @@ -145,15 +163,19 @@ class ProjectController extends PHPCensor\Controller * Render latest builds for project as HTML table. * * @param int $projectId + * @param string $environment A urldecoded environment name. * @param string $branch A urldecoded branch name. * @param int $start * @param int $perPage * * @return array */ - protected function getLatestBuildsHtml($projectId, $branch = '', $start = 0, $perPage = 10) + protected function getLatestBuildsHtml($projectId, $environment = '', $branch = '', $start = 0, $perPage = 10) { $criteria = ['project_id' => $projectId]; + if (!empty($environment)) { + $criteria['environment'] = $environment; + } if (!empty($branch)) { $criteria['branch'] = $branch; } @@ -276,6 +298,8 @@ class ProjectController extends PHPCensor\Controller $values['key'] = $values['ssh_private_key']; $values['pubkey'] = $values['ssh_public_key']; + $values['environments'] = $project->getEnvironments(); + if ($values['type'] == 'gitlab') { $accessInfo = $project->getAccessInformation(); $reference = $accessInfo["user"] . '@' . $accessInfo["domain"] . ':' . $accessInfo["port"] . '/' . ltrim($project->getReference(), '/') . ".git"; @@ -310,6 +334,7 @@ class ProjectController extends PHPCensor\Controller 'archived' => $this->getParam('archived', 0), 'branch' => $this->getParam('branch', null), 'group' => $this->getParam('group_id', null), + 'environments' => $this->getParam('environments', null), ]; $project = $this->projectService->updateProject($project, $title, $type, $reference, $options); @@ -381,6 +406,13 @@ class ProjectController extends PHPCensor\Controller $field->setClass('form-control')->setContainerClass('form-group')->setValue('master'); $form->addField($field); + if ($type != 'add') { + $field = Form\Element\TextArea::create('environments', Lang::get('environments_label'), false); + $field->setClass('form-control')->setContainerClass('form-group'); + $field->setRows(6); + $form->addField($field); + } + $field = Form\Element\Select::create('group_id', Lang::get('project_group'), true); $field->setClass('form-control')->setContainerClass('form-group')->setValue(1); @@ -472,8 +504,9 @@ class ProjectController extends PHPCensor\Controller public function ajaxBuilds($projectId) { $branch = $this->getParam('branch', ''); + $environment = $this->getParam('environment', ''); $perPage = (integer)$this->getParam('per_page', 10); - $builds = $this->getLatestBuildsHtml($projectId, urldecode($branch), 0, $perPage); + $builds = $this->getLatestBuildsHtml($projectId, urldecode($environment), urldecode($branch), 0, $perPage); $this->response->disableLayout(); $this->response->setContent($builds[0]); diff --git a/src/PHPCensor/Controller/WebhookController.php b/src/PHPCensor/Controller/WebhookController.php index 6ac1b5da..98293057 100644 --- a/src/PHPCensor/Controller/WebhookController.php +++ b/src/PHPCensor/Controller/WebhookController.php @@ -6,6 +6,7 @@ use b8; use b8\Store; use Exception; use PHPCensor\Helper\Lang; +use PHPCensor\Model\Build; use PHPCensor\Model\Project; use PHPCensor\Service\BuildService; use PHPCensor\Store\BuildStore; @@ -510,17 +511,57 @@ class WebhookController extends Controller // Check if a build already exists for this commit ID: $builds = $this->buildStore->getByProjectAndCommit($project->getId(), $commitId); + $ignore_environments = []; if ($builds['count']) { - return [ - 'status' => 'ignored', - 'message' => sprintf('Duplicate of build #%d', $builds['items'][0]->getId()) - ]; + foreach($builds['items'] as $build) { + /** @var Build $build */ + $ignore_environments[$build->getId()] = $build->getEnvironment(); + } } - // If not, create a new build job for it: - $build = $this->buildService->createBuild($project, $commitId, $branch, $committer, $commitMessage, $extra); - - return ['status' => 'ok', 'buildID' => $build->getID()]; + $environments = $project->getEnvironmentsObjects(); + if ($environments['count']) { + $created_builds = []; + $environment_names = $project->getEnvironmentsNamesByBranch($branch); + // use base branch from project + if (!empty($environment_names)) { + $duplicates = []; + foreach ($environment_names as $environment_name) { + if (!in_array($environment_name, $ignore_environments)) { + // If not, create a new build job for it: + $build = $this->buildService->createBuild($project, $environment_name, $commitId, $project->getBranch(), $committer, $commitMessage, $extra); + $created_builds[] = array( + 'id' => $build->getID(), + 'environment' => $environment_name, + ); + } else { + $duplicates[] = array_search($environment_name, $ignore_environments); + } + } + if (!empty($created_builds)) { + if (empty($duplicates)) { + return ['status' => 'ok', 'builds' => $created_builds]; + } else { + return ['status' => 'ok', 'builds' => $created_builds, 'message' => sprintf('For this commit some builds already exists (%s)', implode(', ', $duplicates))]; + } + } else { + return ['status' => 'ignored', 'message' => sprintf('For this commit already created builds (%s)', implode(', ', $duplicates))]; + } + } else { + return ['status' => 'ignored', 'message' => 'Branch not assigned to any environment']; + } + } else { + $environment_name = null; + if (!in_array($environment_name, $ignore_environments)) { + $build = $this->buildService->createBuild($project, null, $commitId, $branch, $committer, $commitMessage, $extra); + return ['status' => 'ok', 'buildID' => $build->getID()]; + } else { + return [ + 'status' => 'ignored', + 'message' => sprintf('Duplicate of build #%d', array_search($environment_name, $ignore_environments)), + ]; + } + } } /** diff --git a/src/PHPCensor/Helper/BuildInterpolator.php b/src/PHPCensor/Helper/BuildInterpolator.php index 038715c4..0f74e98b 100644 --- a/src/PHPCensor/Helper/BuildInterpolator.php +++ b/src/PHPCensor/Helper/BuildInterpolator.php @@ -37,6 +37,7 @@ class BuildInterpolator $this->interpolation_vars['%COMMIT_URI%'] = $build->getCommitLink(); $this->interpolation_vars['%BRANCH%'] = $build->getBranch(); $this->interpolation_vars['%BRANCH_URI%'] = $build->getBranchLink(); + $this->interpolation_vars['%ENVIRONMENT%'] = $build->getEnvironment(); $this->interpolation_vars['%PROJECT%'] = $build->getProjectId(); $this->interpolation_vars['%BUILD%'] = $build->getId(); $this->interpolation_vars['%PROJECT_TITLE%'] = $build->getProjectTitle(); diff --git a/src/PHPCensor/Languages/lang.en.php b/src/PHPCensor/Languages/lang.en.php index 8ccb9239..d06ec1f7 100644 --- a/src/PHPCensor/Languages/lang.en.php +++ b/src/PHPCensor/Languages/lang.en.php @@ -48,6 +48,7 @@ PHP Censor', 'branch_x' => 'Branch: %s', 'created_x' => 'Created: %s', 'started_x' => 'Started: %s', + 'environment_x' => 'Environment: %s', // Sidebar 'hello_name' => 'Hello, %s', @@ -117,6 +118,7 @@ PHP Censor', 'archived' => 'Archived', 'archived_menu' => 'Archived', 'save_project' => 'Save Project', + 'environments_label' => 'Environments (yaml)', 'error_mercurial' => 'Mercurial repository URL must be start with http:// or https://', 'error_remote' => 'Repository URL must be start with git://, http:// or https://', @@ -127,12 +129,14 @@ PHP Censor', // View Project: 'all_branches' => 'All Branches', + 'all' => 'All', 'builds' => 'Builds', 'id' => 'ID', 'date' => 'Date', 'project' => 'Project', 'commit' => 'Commit', 'branch' => 'Branch', + 'environment' => 'Environment', 'status' => 'Status', 'prev_link' => '« Prev', 'next_link' => 'Next »', @@ -194,6 +198,7 @@ PHP Censor', 'phpcpd_warnings' => 'PHP Copy/Paste Detector warnings', 'phpdoccheck_warnings' => 'Missing docblocks', 'issues' => 'Issues', + 'merged_branches' => 'Merged branches', 'phpcpd' => 'PHP Copy/Paste Detector', 'phpcs' => 'PHP Code Sniffer', diff --git a/src/PHPCensor/Migrations/20170321131931_add_environment.php b/src/PHPCensor/Migrations/20170321131931_add_environment.php new file mode 100644 index 00000000..f5861ada --- /dev/null +++ b/src/PHPCensor/Migrations/20170321131931_add_environment.php @@ -0,0 +1,52 @@ +table('environment'); + + if (!$this->hasTable('environment')) { + $table->create(); + } + + if (!$table->hasColumn('project_id')) { + $table->addColumn('project_id', 'integer')->save(); + } + + if (!$table->hasColumn('name')) { + $table->addColumn('name', 'string', ['limit' => 20])->save(); + } + + if (!$table->hasColumn('branches')) { + $table->addColumn('branches', 'text')->save(); + } + + if (!$table->hasIndex(['project_id', 'name'])) { + $table->addIndex(['project_id', 'name'])->save(); + } + + $table = $this->table('build'); + + if (!$table->hasColumn('environment')) { + $table->addColumn('environment', 'string', ['limit' => 20])->save(); + } + } + + public function down() + { + $table = $this->table('environment'); + + if ($this->hasTable('environment')) { + $table->drop(); + } + + $table = $this->table('build'); + + if ($table->hasColumn('environment')) { + $table->removeColumn('environment')->save(); + } + } +} diff --git a/src/PHPCensor/Model/Build.php b/src/PHPCensor/Model/Build.php index 2af6da05..c62fa666 100644 --- a/src/PHPCensor/Model/Build.php +++ b/src/PHPCensor/Model/Build.php @@ -52,6 +52,7 @@ class Build extends Model 'committer_email' => null, 'commit_message' => null, 'extra' => null, + 'environment' => null, ]; /** @@ -71,6 +72,7 @@ class Build extends Model 'committer_email' => 'getCommitterEmail', 'commit_message' => 'getCommitMessage', 'extra' => 'getExtra', + 'environment' => 'getEnvironment', // Foreign key getters: 'Project' => 'getProject', @@ -93,6 +95,7 @@ class Build extends Model 'committer_email' => 'setCommitterEmail', 'commit_message' => 'setCommitMessage', 'extra' => 'setExtra', + 'environment' => 'setEnvironment', // Foreign key setters: 'Project' => 'setProject', @@ -165,6 +168,11 @@ class Build extends Model 'nullable' => true, 'default' => null, ], + 'environment' => [ + 'type' => 'varchar', + 'length' => 20, + 'default' => null, + ], ]; /** @@ -542,6 +550,38 @@ class Build extends Model $this->setModified('extra'); } + /** + * Set the value of Extra / extra. + * + * @param $name string + * @param $value mixed + */ + public function setExtraValue($name, $value) + { + $extra = json_decode($this->data['extra'], true); + if ($extra === false) { + $extra = []; + } + $extra[$name] = $value; + $this->setExtra(json_encode($extra)); + } + + /** + * Set the values of Extra / extra. + * + * @param $name string + * @param $values mixed + */ + public function setExtraValues($values) + { + $extra = json_decode($this->data['extra'], true); + if ($extra === false) { + $extra = []; + } + $extra = array_replace($extra, $values); + $this->setExtra(json_encode($extra)); + } + /** * Get the Project model for this Build by Id. * @@ -931,6 +971,36 @@ class Build extends Model return false; } + /** + * Get the value of Environment / environment. + * + * @return string + */ + public function getEnvironment() + { + $rtn = $this->data['environment']; + + return $rtn; + } + + /** + * Set the value of Environment / environment. + * + * @param $value string + */ + public function setEnvironment($value) + { + $this->validateString('Environment', $value); + + if ($this->data['environment'] === $value) { + return; + } + + $this->data['environment'] = $value; + + $this->setModified('environment'); + } + /** * Create an SSH key file on disk for this build. * diff --git a/src/PHPCensor/Model/Build/RemoteGitBuild.php b/src/PHPCensor/Model/Build/RemoteGitBuild.php index 1edc7238..d39fd17c 100644 --- a/src/PHPCensor/Model/Build/RemoteGitBuild.php +++ b/src/PHPCensor/Model/Build/RemoteGitBuild.php @@ -4,6 +4,7 @@ namespace PHPCensor\Model\Build; use PHPCensor\Model\Build; use PHPCensor\Builder; +use Psr\Log\LogLevel; /** * Remote Git Build Model @@ -33,6 +34,10 @@ class RemoteGitBuild extends Build $success = $this->cloneByHttp($builder, $buildPath); } + if ($success) { + $success = $this->mergeBranches($builder, $buildPath); + } + if (!$success) { $builder->logFailure('Failed to clone remote git repository.'); return false; @@ -41,6 +46,28 @@ class RemoteGitBuild extends Build return $this->handleConfig($builder, $buildPath); } + /** + * @param Builder $builder + * @param string $buildPath + * @return bool + */ + protected function mergeBranches(Builder $builder, $buildPath) + { + $branches = $this->getExtra('branches'); + if (!empty($branches)) { + $cmd = 'cd "%s" && git merge --quiet origin/%s'; + foreach ($branches as $branch) { + $success = $builder->executeCommand($cmd, $buildPath, $branch); + if (!$success) { + $builder->log('Fail merge branch origin/'.$branch, LogLevel::ERROR); + return false; + } + $builder->log('Merged branch origin/'.$branch, LogLevel::INFO); + } + } + return true; + } + /** * Use an HTTP-based git clone. */ diff --git a/src/PHPCensor/Model/Environment.php b/src/PHPCensor/Model/Environment.php new file mode 100644 index 00000000..5689dd05 --- /dev/null +++ b/src/PHPCensor/Model/Environment.php @@ -0,0 +1,233 @@ + null, + 'project_id' => null, + 'name' => null, + 'branches' => null, + ]; + + /** + * @var array + */ + protected $getters = [ + // Direct property getters: + 'id' => 'getId', + 'project_id' => 'getProjectId', + 'name' => 'getName', + 'branches' => 'getBranches', + // Foreign key getters: + ]; + + /** + * @var array + */ + protected $setters = [ + // Direct property setters: + 'id' => 'setId', + 'project_id' => 'setProjectId', + 'name' => 'setName', + 'branches' => 'setBranches', + // Foreign key setters: + ]; + + /** + * @var array + */ + public $columns = [ + 'id' => [ + 'type' => 'int', + 'length' => 11, + 'primary_key' => true, + 'auto_increment' => true, + 'default' => null, + ], + 'project_id' => [ + 'type' => 'int', + 'length' => 11, + 'primary_key' => true, + 'default' => null, + ], + 'name' => [ + 'type' => 'varchar', + 'length' => 20, + 'default' => null, + ], + 'branches' => [ + 'type' => 'text', + 'default' => '', + ], + ]; + + /** + * @var array + */ + public $indexes = [ + 'PRIMARY' => ['unique' => true, 'columns' => 'project_id, name'], + ]; + + /** + * @var array + */ + public $foreignKeys = [ + 'environment_ibfk_1' => [ + 'local_col' => 'project_id', + 'update' => 'CASCADE', + 'delete' => '', + 'table' => 'project', + 'col' => 'id' + ], + ]; + + /** + * Get the value of Id / id. + * + * @return int + */ + public function getId() + { + $rtn = $this->data['id']; + + return $rtn; + } + + /** + * Get the value of Id / id. + * + * @return int + */ + public function getProjectId() + { + $rtn = $this->data['project_id']; + + return $rtn; + } + + /** + * Get the value of Title / title. + * + * @return string + */ + public function getName() + { + $rtn = $this->data['name']; + + return $rtn; + } + + /** + * Get the value of Title / title. + * + * @return string + */ + public function getBranches() + { + $rtn = array_filter(array_map('trim', explode("\n", $this->data['branches']))); + + 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 Id / id. + * + * Must not be null. + * @param $value int + */ + public function setProjectId($value) + { + $this->validateNotNull('ProjectId', $value); + $this->validateInt('ProjectId', $value); + + if ($this->data['project_id'] === $value) { + return; + } + + $this->data['project_id'] = $value; + + $this->setModified('project_id'); + } + + /** + * Set the value of Name / name + * + * Must not be null. + * @param $value string + */ + public function setName($value) + { + $this->validateNotNull('Name', $value); + $this->validateString('Name', $value); + + if ($this->data['name'] === $value) { + return; + } + + $this->data['name'] = $value; + + $this->setModified('name'); + } + + /** + * Set the value of Branches / branches + * + * Must not be null. + * @param $value array + */ + public function setBranches($value) + { + $this->validateNotNull('Branches', $value); + $value = implode("\n", $value); + + if ($this->data['branches'] === $value) { + return; + } + + $this->data['branches'] = $value; + + $this->setModified('branches'); + } +} diff --git a/src/PHPCensor/Model/Project.php b/src/PHPCensor/Model/Project.php index 361a3f34..96073970 100644 --- a/src/PHPCensor/Model/Project.php +++ b/src/PHPCensor/Model/Project.php @@ -5,6 +5,9 @@ namespace PHPCensor\Model; use PHPCensor\Model; use b8\Store; use b8\Store\Factory; +use PHPCensor\Store\EnvironmentStore; +use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Yaml\Dumper as YamlDumper; /** * @author Dan Cryer @@ -767,4 +770,135 @@ class Project extends Model return $icon; } + + /** + * Get Environments + * + * @return array contain items with \PHPCensor\Model\Environment + */ + public function getEnvironmentsObjects() + { + $key = $this->getId(); + + if (empty($key)) { + return null; + } + + $cacheKey = 'Cache.ProjectEnvironments.' . $key; + $rtn = $this->cache->get($cacheKey, null); + + if (empty($rtn)) { + /** @var EnvironmentStore $store */ + $store = Factory::getStore('Environment', 'PHPCensor'); + $rtn = $store->getByProjectId($key); + $this->cache->set($cacheKey, $rtn); + } + + return $rtn; + } + + /** + * Get Environments + * + * @return string[] + */ + public function getEnvironmentsNames() + { + $environments = $this->getEnvironmentsObjects(); + $environments_names = []; + foreach($environments['items'] as $environment) { + /** @var Environment $environment */ + $environments_names[] = $environment->getName(); + } + return $environments_names; + } + + /** + * Get Environments + * + * @return string yaml + */ + public function getEnvironments() + { + $environments = $this->getEnvironmentsObjects(); + $environments_config = []; + foreach($environments['items'] as $environment) { + /** @var Environment $environment */ + $environments_config[$environment->getName()] = $environment->getBranches(); + } + $yaml_dumper = new YamlDumper(); + $value = $yaml_dumper->dump($environments_config, 10, 0, true, false); + return $value; + } + + /** + * Set Environments + * + * @param string $value yaml + */ + public function setEnvironments($value) + { + $yaml_parser = new YamlParser(); + $environments_config = $yaml_parser->parse($value); + $environments_names = !empty($environments_config) ? array_keys($environments_config) : []; + + $current_environments = $this->getEnvironmentsObjects(); + $store = Factory::getStore('Environment', 'PHPCensor'); + foreach ($current_environments['items'] as $environment) { + /** @var Environment $environment */ + $key = array_search($environment->getName(), $environments_names); + if ($key !== false) { + // already exist + unset($environments_names[$key]); + $environment->setBranches(!empty($environments_config[$environment->getName()]) ? $environments_config[$environment->getName()] : []); + } else { + // remove + $store->delete($environment); + } + } + if (!empty($environments_names)) { + // add + foreach ($environments_names as $environment_name) { + $environment = new Environment(); + $environment->setProjectId($this->getId()); + $environment->setName($environment_name); + $environment->setBranches(!empty($environments_config[$environment->getName()]) ? $environments_config[$environment->getName()] : []); + $store->save($environment); + } + } + } + + /** + * @param string $branch + * @return string[] + */ + public function getEnvironmentsNamesByBranch($branch) + { + $environments_names = []; + $environments = $this->getEnvironmentsObjects(); + foreach($environments['items'] as $environment) { + /** @var Environment $environment */ + if (in_array($branch, $environment->getBranches())) { + $environments_names[] = $environment->getName(); + } + } + return $environments_names; + } + + /** + * @param string $environment_name + * @return string[] + */ + public function getBranchesByEnvironment($environment_name) + { + $branches = []; + $environments = $this->getEnvironmentsObjects(); + foreach($environments['items'] as $environment) { + /** @var Environment $environment */ + if ($environment_name == $environment->getName()) { + return $environment->getBranches(); + } + } + return $branches; + } } diff --git a/src/PHPCensor/Service/BuildService.php b/src/PHPCensor/Service/BuildService.php index 7e26b219..332e4dc8 100644 --- a/src/PHPCensor/Service/BuildService.php +++ b/src/PHPCensor/Service/BuildService.php @@ -35,6 +35,7 @@ class BuildService /** * @param Project $project + * @param string $environment * @param string|null $commitId * @param string|null $branch * @param string|null $committerEmail @@ -44,6 +45,7 @@ class BuildService */ public function createBuild( Project $project, + $environment, $commitId = null, $branch = null, $committerEmail = null, @@ -54,6 +56,10 @@ class BuildService $build->setCreated(new \DateTime()); $build->setProject($project); $build->setStatus(0); + $build->setEnvironment($environment); + + $branches = $project->getBranchesByEnvironment($environment); + $build->setExtraValue('branches', $branches); if (!is_null($commitId)) { $build->setCommitId($commitId); @@ -77,7 +83,7 @@ class BuildService } if (!is_null($extra)) { - $build->setExtra(json_encode($extra)); + $build->setExtraValues($extra); } $build = $this->buildStore->save($build); diff --git a/src/PHPCensor/Service/ProjectService.php b/src/PHPCensor/Service/ProjectService.php index a52fb273..79688828 100644 --- a/src/PHPCensor/Service/ProjectService.php +++ b/src/PHPCensor/Service/ProjectService.php @@ -84,6 +84,10 @@ class ProjectService $project->setGroup($options['group']); } + if (array_key_exists('environments', $options)) { + $project->setEnvironments($options['environments']); + } + // Allow certain project types to set access information: $this->processAccessInformation($project); diff --git a/src/PHPCensor/Store/EnvironmentStore.php b/src/PHPCensor/Store/EnvironmentStore.php new file mode 100644 index 00000000..54f48bed --- /dev/null +++ b/src/PHPCensor/Store/EnvironmentStore.php @@ -0,0 +1,89 @@ +getById($value, $useConnection); + } + + /** + * Get a single Environment by Id. + * @param $value + * @param string $useConnection + * @return null|Environment + * @throws HttpException + */ + public function getById($value, $useConnection = 'read') + { + if (is_null($value)) { + throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); + } + + $query = 'SELECT * FROM {{environment}} WHERE {{id}} = :id LIMIT 1'; + $stmt = Database::getConnection($useConnection)->prepareCommon($query); + $stmt->bindValue(':id', $value); + + if ($stmt->execute()) { + if ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return new Environment($data); + } + } + + return null; + } + + /** + * Get multiple Environment by Project id. + * + * @param integer $value + * @param string $useConnection + * + * @return array + * + * @throws \Exception + */ + public function getByProjectId($value, $useConnection = 'read') + { + if (is_null($value)) { + throw new \Exception('Value passed to ' . __FUNCTION__ . ' cannot be null.'); + } + + $query = 'SELECT * FROM {{environment}} WHERE {{project_id}} = :project_id'; + $stmt = Database::getConnection($useConnection)->prepareCommon($query); + + $stmt->bindValue(':project_id', $value); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return new Environment($item); + }; + $rtn = array_map($map, $res); + + $count = count($rtn); + + return ['items' => $rtn, 'count' => $count]; + } else { + return ['items' => [], 'count' => 0]; + } + } +} diff --git a/src/PHPCensor/View/Build/view.phtml b/src/PHPCensor/View/Build/view.phtml index 2b515a02..2978398d 100644 --- a/src/PHPCensor/View/Build/view.phtml +++ b/src/PHPCensor/View/Build/view.phtml @@ -22,18 +22,26 @@ - + - - getBranch(); ?> - + getEnvironment(); ?> - - - getDuration(); ?> + + + getBranch() ?> + + getExtra('branches'); + if (!empty($branches)) { + foreach($branches as $branch) { + ?> @@ -51,6 +59,15 @@
+ + + + + + + + + +
+ + getBranch(); ?> + +
@@ -107,6 +124,13 @@ getFinished() ? $build->getFinished()->format('Y-m-d H:i:s') : ''; ?>
+ getDuration(); ?> +
diff --git a/src/PHPCensor/View/Home/ajax-timeline.phtml b/src/PHPCensor/View/Home/ajax-timeline.phtml index e2144337..2b3e9a40 100644 --- a/src/PHPCensor/View/Home/ajax-timeline.phtml +++ b/src/PHPCensor/View/Home/ajax-timeline.phtml @@ -4,6 +4,9 @@ getEnvironment(); + $branches = $build->getExtra('branches'); + switch ($build->getStatus()) { case \PHPCensor\Model\Build::STATUS_PENDING: $updated = $build->getCreated(); @@ -60,6 +63,7 @@ getProject()->getTitle(); ?> + - Build #getId(); ?> @@ -69,7 +73,8 @@
- getProject()->getBranch(); ?> - + getProject()->getBranch(); ?> + - getCommitId() !== 'Manual') { print sprintf( diff --git a/src/PHPCensor/View/Home/index.phtml b/src/PHPCensor/View/Home/index.phtml index 5ceecd2c..e1c4f228 100644 --- a/src/PHPCensor/View/Home/index.phtml +++ b/src/PHPCensor/View/Home/index.phtml @@ -30,6 +30,9 @@ getEnvironment(); + $branches = $build->getExtra('branches'); + switch ($build->getStatus()) { case \PHPCensor\Model\Build::STATUS_PENDING: $updated = $build->getCreated(); @@ -86,6 +89,7 @@ getProject()->getTitle(); ?> + - Build #getId(); ?> @@ -95,7 +99,8 @@
- getProject()->getBranch(); ?> - + getProject()->getBranch(); ?> + - getCommitId() !== 'Manual') { print sprintf( diff --git a/src/PHPCensor/View/Project/ajax-builds.phtml b/src/PHPCensor/View/Project/ajax-builds.phtml index 4d934ea1..8e1f7639 100644 --- a/src/PHPCensor/View/Project/ajax-builds.phtml +++ b/src/PHPCensor/View/Project/ajax-builds.phtml @@ -36,6 +36,8 @@ switch($build->getStatus()) $status = Lang::get('failed'); break; } + +$branches = $build->getExtra('branches'); ?> #getId(), 6, '0', STR_PAD_LEFT); ?> @@ -55,7 +57,11 @@ switch($build->getStatus()) ?> - getBranch(); ?> + getEnvironment(); ?> + + getBranch(); ?> + + diff --git a/src/PHPCensor/View/Project/edit.phtml b/src/PHPCensor/View/Project/edit.phtml index 0d4e8789..69a12fc3 100644 --- a/src/PHPCensor/View/Project/edit.phtml +++ b/src/PHPCensor/View/Project/edit.phtml @@ -8,6 +8,11 @@ lineWrapping: true, lineNumbers: true }); + CodeMirror.fromTextArea(document.getElementById('element-environments'), { + mode: "yaml", + lineWrapping: true, + lineNumbers: true + }); setupProjectForm(); }); diff --git a/src/PHPCensor/View/Project/view.phtml b/src/PHPCensor/View/Project/view.phtml index 6654edf3..c3f82950 100644 --- a/src/PHPCensor/View/Project/view.phtml +++ b/src/PHPCensor/View/Project/view.phtml @@ -1,6 +1,7 @@ @@ -17,32 +18,97 @@
getArchived()): ?> User()->getIsAdmin()): ?> - - - +
+ + + +
- - - +
+ + + +
-
+
@@ -61,6 +127,7 @@ + diff --git a/tests/PHPCensor/Service/BuildServiceTest.php b/tests/PHPCensor/Service/BuildServiceTest.php index 6c8fa1a6..43268766 100644 --- a/tests/PHPCensor/Service/BuildServiceTest.php +++ b/tests/PHPCensor/Service/BuildServiceTest.php @@ -40,7 +40,7 @@ class BuildServiceTest extends \PHPUnit_Framework_TestCase $project->setType('github'); $project->setId(101); - $returnValue = $this->testedService->createBuild($project); + $returnValue = $this->testedService->createBuild($project, null); $this->assertEquals(101, $returnValue->getProjectId()); $this->assertEquals(Build::STATUS_PENDING, $returnValue->getStatus()); @@ -61,7 +61,7 @@ class BuildServiceTest extends \PHPUnit_Framework_TestCase $project->setType('hg'); $project->setId(101); - $returnValue = $this->testedService->createBuild($project, '123', 'testbranch', 'test@example.com', 'test'); + $returnValue = $this->testedService->createBuild($project, null, '123', 'testbranch', 'test@example.com', 'test'); $this->assertEquals('testbranch', $returnValue->getBranch()); $this->assertEquals('123', $returnValue->getCommitId()); @@ -75,7 +75,7 @@ class BuildServiceTest extends \PHPUnit_Framework_TestCase $project->setType('bitbucket'); $project->setId(101); - $returnValue = $this->testedService->createBuild($project, null, null, null, null, ['item1' => 1001]); + $returnValue = $this->testedService->createBuild($project, null, null, null, null, null, ['item1' => 1001]); $this->assertEquals(1001, $returnValue->getExtra('item1')); }