diff --git a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php --- a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php +++ b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php @@ -60,12 +60,13 @@ 'delete/(?:(?P\d+)/)?' => 'HarbormasterStepDeleteController', ), 'buildable/' => array( - '(?P\d+)/(?Pstop|resume|restart)/' + '(?P\d+)/(?Pstop|resume|restart|abort)/' => 'HarbormasterBuildableActionController', ), 'build/' => array( '(?P\d+)/' => 'HarbormasterBuildViewController', - '(?Pstop|resume|restart)/(?P\d+)/(?:(?P[^/]+)/)?' + '(?Pstop|resume|restart|abort)/'. + '(?P\d+)/(?:(?P[^/]+)/)?' => 'HarbormasterBuildActionController', ), 'plan/' => array( 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 @@ -41,6 +41,9 @@ case HarbormasterBuildCommand::COMMAND_RESUME: $can_issue = $build->canResumeBuild(); break; + case HarbormasterBuildCommand::COMMAND_ABORT: + $can_issue = $build->canAbortBuild(); + break; default: return new Aphront400Response(); } @@ -90,6 +93,18 @@ } } break; + case HarbormasterBuildCommand::COMMAND_ABORT: + if ($can_issue) { + $title = pht('Really abort build?'); + $body = pht( + 'Progress on this build will be discarded. Really '. + 'abort build?'); + $submit = pht('Abort Build'); + } else { + $title = pht('Unable to Abort Build'); + $body = pht('You can not abort this build.'); + } + break; case HarbormasterBuildCommand::COMMAND_STOP: if ($can_issue) { $title = pht('Really pause build?'); diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -39,6 +39,8 @@ $header->setStatus('fa-exclamation-triangle', 'red', pht('Pausing')); } else if ($build->isResuming()) { $header->setStatus('fa-exclamation-triangle', 'red', pht('Resuming')); + } else if ($build->isAborting()) { + $header->setStatus('fa-exclamation-triangle', 'red', pht('Aborting')); } $box = id(new PHUIObjectBoxView()) @@ -415,6 +417,7 @@ $can_restart = $build->canRestartBuild(); $can_stop = $build->canStopBuild(); $can_resume = $build->canResumeBuild(); + $can_abort = $build->canAbortBuild(); $list->addAction( id(new PhabricatorActionView()) @@ -442,6 +445,14 @@ ->setWorkflow(true)); } + $list->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Abort Build')) + ->setIcon('fa-exclamation-triangle') + ->setHref($this->getApplicationURI('/build/abort/'.$id.'/')) + ->setDisabled(!$can_abort) + ->setWorkflow(true)); + return $list; } diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableActionController.php b/src/applications/harbormaster/controller/HarbormasterBuildableActionController.php --- a/src/applications/harbormaster/controller/HarbormasterBuildableActionController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableActionController.php @@ -49,6 +49,11 @@ $issuable[] = $build; } break; + case HarbormasterBuildCommand::COMMAND_ABORT: + if ($build->canAbortBuild()) { + $issuable[] = $build; + } + break; default: return new Aphront400Response(); } @@ -110,6 +115,17 @@ $body = pht('No builds can be stopped.'); } break; + case HarbormasterBuildCommand::COMMAND_ABORT: + if ($issuable) { + $title = pht('Really abort all builds?'); + $body = pht( + 'If you abort all builds, work will halt immediately.'); + $submit = pht('Abort All Builds'); + } else { + $title = pht('Unable to Abort Builds'); + $body = pht('No builds can be aborted.'); + } + break; case HarbormasterBuildCommand::COMMAND_RESUME: if ($issuable) { $title = pht('Really resume all builds?'); diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -85,6 +85,7 @@ $can_restart = false; $can_resume = false; $can_stop = false; + $can_abort = false; foreach ($buildable->getBuilds() as $build) { if ($build->canRestartBuild()) { @@ -96,11 +97,15 @@ if ($build->canStopBuild()) { $can_stop = true; } + if ($build->canAbortBuild()) { + $can_abort = true; + } } $restart_uri = "buildable/{$id}/restart/"; $stop_uri = "buildable/{$id}/stop/"; $resume_uri = "buildable/{$id}/resume/"; + $abort_uri = "buildable/{$id}/abort/"; $list->addAction( id(new PhabricatorActionView()) @@ -126,6 +131,14 @@ ->setWorkflow(true) ->setDisabled(!$can_resume || !$can_edit)); + $list->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-exclamation-triangle') + ->setName(pht('Abort All Builds')) + ->setHref($this->getApplicationURI($abort_uri)) + ->setWorkflow(true) + ->setDisabled(!$can_abort || !$can_edit)); + return $list; } @@ -190,6 +203,7 @@ $restart_uri = "build/restart/{$build_id}/buildable/"; $resume_uri = "build/resume/{$build_id}/buildable/"; $stop_uri = "build/stop/{$build_id}/buildable/"; + $abort_uri = "build/abort/{$build_id}/buildable/"; $item->addAction( id(new PHUIListItemView()) diff --git a/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php --- a/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php +++ b/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php @@ -77,6 +77,9 @@ case HarbormasterBuildCommand::COMMAND_RESUME: $issuable = $build->canResumeBuild(); break; + case HarbormasterBuildCommand::COMMAND_ABORT: + $issuable = $build->canAbortBuild(); + break; default: throw new Exception(pht('Unknown command %s', $command)); } diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -98,21 +98,27 @@ } private function updateBuild(HarbormasterBuild $build) { - if (($build->getBuildStatus() == HarbormasterBuild::STATUS_PENDING) || - ($build->isRestarting())) { + if ($build->isAborting()) { $this->restartBuild($build); - $build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING); + $build->setBuildStatus(HarbormasterBuild::STATUS_ABORTED); $build->save(); - } + } else { + if (($build->getBuildStatus() == HarbormasterBuild::STATUS_PENDING) || + ($build->isRestarting())) { + $this->restartBuild($build); + $build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING); + $build->save(); + } - if ($build->isResuming()) { - $build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING); - $build->save(); - } + if ($build->isResuming()) { + $build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING); + $build->save(); + } - if ($build->isStopping() && !$build->isComplete()) { - $build->setBuildStatus(HarbormasterBuild::STATUS_STOPPED); - $build->save(); + if ($build->isStopping() && !$build->isComplete()) { + $build->setBuildStatus(HarbormasterBuild::STATUS_STOPPED); + $build->save(); + } } $build->deleteUnprocessedCommands(); diff --git a/src/applications/harbormaster/storage/HarbormasterBuildCommand.php b/src/applications/harbormaster/storage/HarbormasterBuildCommand.php --- a/src/applications/harbormaster/storage/HarbormasterBuildCommand.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildCommand.php @@ -5,6 +5,7 @@ const COMMAND_STOP = 'stop'; const COMMAND_RESUME = 'resume'; const COMMAND_RESTART = 'restart'; + const COMMAND_ABORT = 'abort'; protected $authorPHID; protected $targetPHID; diff --git a/src/applications/harbormaster/storage/HarbormasterBuildTransaction.php b/src/applications/harbormaster/storage/HarbormasterBuildTransaction.php --- a/src/applications/harbormaster/storage/HarbormasterBuildTransaction.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildTransaction.php @@ -31,6 +31,10 @@ return pht( '%s restarted this build.', $this->renderHandleLink($author_phid)); + case HarbormasterBuildCommand::COMMAND_ABORT: + return pht( + '%s aborted this build.', + $this->renderHandleLink($author_phid)); case HarbormasterBuildCommand::COMMAND_RESUME: return pht( '%s resumed this build.', @@ -61,6 +65,8 @@ return 'fa-play'; case HarbormasterBuildCommand::COMMAND_STOP: return 'fa-stop'; + case HarbormasterBuildCommand::COMMAND_ABORT: + return 'fa-exclamation-triangle'; } } @@ -79,6 +85,7 @@ case self::TYPE_COMMAND: switch ($new) { case HarbormasterBuildCommand::COMMAND_STOP: + case HarbormasterBuildCommand::COMMAND_ABORT: return '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 @@ -42,6 +42,11 @@ const STATUS_FAILED = 'failed'; /** + * The build has aborted. + */ + const STATUS_ABORTED = 'aborted'; + + /** * The build encountered an unexpected error. */ const STATUS_ERROR = 'error'; @@ -75,6 +80,8 @@ return pht('Passed'); case self::STATUS_FAILED: return pht('Failed'); + case self::STATUS_ABORTED: + return pht('Aborted'); case self::STATUS_ERROR: return pht('Unexpected Error'); case self::STATUS_STOPPED: @@ -97,6 +104,8 @@ return PHUIStatusItemView::ICON_ACCEPT; case self::STATUS_FAILED: return PHUIStatusItemView::ICON_REJECT; + case self::STATUS_ABORTED: + return PHUIStatusItemView::ICON_MINUS; case self::STATUS_ERROR: return PHUIStatusItemView::ICON_MINUS; case self::STATUS_STOPPED: @@ -118,6 +127,7 @@ case self::STATUS_PASSED: return 'green'; case self::STATUS_FAILED: + case self::STATUS_ABORTED: case self::STATUS_ERROR: case self::STATUS_DEADLOCKED: return 'red'; @@ -310,6 +320,7 @@ switch ($this->getBuildStatus()) { case self::STATUS_PASSED: case self::STATUS_FAILED: + case self::STATUS_ABORTED: case self::STATUS_ERROR: case self::STATUS_STOPPED: return true; @@ -345,6 +356,10 @@ !$this->isStopping(); } + public function canAbortBuild() { + return !$this->isComplete(); + } + public function canResumeBuild() { return $this->isStopped() && !$this->isResuming(); @@ -362,6 +377,9 @@ case HarbormasterBuildCommand::COMMAND_RESTART: $is_stopping = false; break; + case HarbormasterBuildCommand::COMMAND_ABORT: + $is_stopping = true; + break; } } @@ -380,6 +398,9 @@ case HarbormasterBuildCommand::COMMAND_STOP: $is_resuming = false; break; + case HarbormasterBuildCommand::COMMAND_ABORT: + $is_resuming = false; + break; } } @@ -400,6 +421,20 @@ return $is_restarting; } + public function isAborting() { + $is_aborting = false; + foreach ($this->getUnprocessedCommands() as $command_object) { + $command = $command_object->getCommand(); + switch ($command) { + case HarbormasterBuildCommand::COMMAND_ABORT: + $is_aborting = true; + break; + } + } + + return $is_aborting; + } + public function deleteUnprocessedCommands() { foreach ($this->getUnprocessedCommands() as $key => $command_object) { $command_object->delete(); diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -220,6 +220,7 @@ public function isFailed() { switch ($this->getTargetStatus()) { case self::STATUS_FAILED: + case self::STATUS_ABORTED: return true; }