diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1443,6 +1443,7 @@ 'HarbormasterQueryBuildsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php', 'HarbormasterQueryBuildsSearchEngineAttachment' => 'applications/harbormaster/engineextension/HarbormasterQueryBuildsSearchEngineAttachment.php', 'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php', + 'HarbormasterRestartException' => 'applications/harbormaster/exception/HarbormasterRestartException.php', 'HarbormasterRunBuildPlansHeraldAction' => 'applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php', 'HarbormasterSchemaSpec' => 'applications/harbormaster/storage/HarbormasterSchemaSpec.php', 'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php', @@ -7093,6 +7094,7 @@ 'HarbormasterQueryBuildsConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterQueryBuildsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'HarbormasterRemarkupRule' => 'PhabricatorObjectRemarkupRule', + 'HarbormasterRestartException' => 'Exception', 'HarbormasterRunBuildPlansHeraldAction' => 'HeraldAction', 'HarbormasterSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'HarbormasterScratchTable' => 'HarbormasterDAO', diff --git a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php --- a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php +++ b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php @@ -52,6 +52,10 @@ return ($this->key === self::STATUS_PASSED); } + public function isFailed() { + return ($this->key === self::STATUS_FAILED); + } + /** * Get a human readable name for a build status constant. diff --git a/src/applications/harbormaster/controller/HarbormasterBuildActionController.php b/src/applications/harbormaster/controller/HarbormasterBuildActionController.php --- a/src/applications/harbormaster/controller/HarbormasterBuildActionController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildActionController.php @@ -64,19 +64,13 @@ 'restart. Side effects of the build will occur again. Really '. 'restart build?'); $submit = pht('Restart Build'); - } else if (!$build->getBuildPlan()->canRestartBuildPlan()) { - $title = pht('Not Restartable'); - $body = pht( - 'The build plan for this build is not restartable, so you '. - 'can not restart the build.'); } else { - $title = pht('Unable to Restart Build'); - if ($build->isRestarting()) { - $body = pht( - 'This build is already restarting. You can not reissue a '. - 'restart command to a restarting build.'); - } else { - $body = pht('You can not restart this build.'); + try { + $build->assertCanRestartBuild(); + throw new Exception(pht('Expected to be unable to restart build.')); + } catch (HarbormasterRestartException $ex) { + $title = $ex->getTitle(); + $body = $ex->getBody(); } } break; diff --git a/src/applications/harbormaster/exception/HarbormasterRestartException.php b/src/applications/harbormaster/exception/HarbormasterRestartException.php new file mode 100644 --- /dev/null +++ b/src/applications/harbormaster/exception/HarbormasterRestartException.php @@ -0,0 +1,33 @@ +setTitle($title); + $this->appendParagraph($body); + + parent::__construct($title); + } + + public function setTitle($title) { + $this->title = $title; + return $this; + } + + public function getTitle() { + return $this->title; + } + + public function appendParagraph($description) { + $this->body[] = $description; + return $this; + } + + public function getBody() { + return $this->body; + } + +} diff --git a/src/applications/harbormaster/plan/HarbormasterBuildPlanBehavior.php b/src/applications/harbormaster/plan/HarbormasterBuildPlanBehavior.php --- a/src/applications/harbormaster/plan/HarbormasterBuildPlanBehavior.php +++ b/src/applications/harbormaster/plan/HarbormasterBuildPlanBehavior.php @@ -15,6 +15,7 @@ const BEHAVIOR_RESTARTABLE = 'restartable'; const RESTARTABLE_ALWAYS = 'always'; + const RESTARTABLE_IF_FAILED = 'failed'; const RESTARTABLE_NEVER = 'never'; const BEHAVIOR_DRAFTS = 'hold-drafts'; @@ -251,6 +252,12 @@ ->setIsDefault(true) ->setDescription( pht('The build may be restarted.')), + id(new HarbormasterBuildPlanBehaviorOption()) + ->setKey(self::RESTARTABLE_IF_FAILED) + ->setIcon('fa-times-circle-o yellow') + ->setName(pht('If Failed')) + ->setDescription( + pht('The build may be restarted if it has failed.')), id(new HarbormasterBuildPlanBehaviorOption()) ->setKey(self::RESTARTABLE_NEVER) ->setIcon('fa-times red') diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -183,6 +183,10 @@ return $this->getBuildStatusObject()->isPassed(); } + public function isFailed() { + return $this->getBuildStatusObject()->isFailed(); + } + public function getURI() { $id = $this->getID(); return "/harbormaster/build/{$id}/"; @@ -211,16 +215,60 @@ } public function canRestartBuild() { - if ($this->isAutobuild()) { + try { + $this->assertCanRestartBuild(); + return true; + } catch (HarbormasterRestartException $ex) { return false; } + } + + public function assertCanRestartBuild() { + if ($this->isAutobuild()) { + throw new HarbormasterRestartException( + pht('Can Not Restart Autobuild'), + pht( + 'This build can not be restarted because it is an automatic '. + 'build.')); + } + $restartable = HarbormasterBuildPlanBehavior::BEHAVIOR_RESTARTABLE; $plan = $this->getBuildPlan(); - if (!$plan->canRestartBuildPlan()) { - return false; + + $option = HarbormasterBuildPlanBehavior::getBehavior($restartable) + ->getPlanOption($plan); + $option_key = $option->getKey(); + + $never_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_NEVER; + $is_never = ($option_key === $never_restartable); + if ($is_never) { + throw new HarbormasterRestartException( + pht('Build Plan Prevents Restart'), + pht( + 'This build can not be restarted because the build plan is '. + 'configured to prevent the build from restarting.')); + } + + $failed_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_IF_FAILED; + $is_failed = ($option_key === $failed_restartable); + if ($is_failed) { + if (!$this->isFailed()) { + throw new HarbormasterRestartException( + pht('Only Restartable if Failed'), + pht( + 'This build can not be restarted because the build plan is '. + 'configured to prevent the build from restarting unless it '. + 'has failed, and it has not failed.')); + } } - return !$this->isRestarting(); + if ($this->isRestarting()) { + throw new HarbormasterRestartException( + pht('Already Restarting'), + pht( + 'This build is already restarting. You can not reissue a restart '. + 'command to a restarting build.')); + } } public function canPauseBuild() { diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php --- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php @@ -175,16 +175,6 @@ $capability); } - public function canRestartBuildPlan() { - $restartable = HarbormasterBuildPlanBehavior::BEHAVIOR_RESTARTABLE; - $is_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_ALWAYS; - - $option = HarbormasterBuildPlanBehavior::getBehavior($restartable) - ->getPlanOption($this); - - return ($option->getKey() === $is_restartable); - } - /* -( PhabricatorSubscribableInterface )----------------------------------- */