diff --git a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php index 341bf4b7ab..a2cdba5b63 100644 --- a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php +++ b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php @@ -1,121 +1,145 @@ pht('Inactive'), self::STATUS_PENDING => pht('Pending'), self::STATUS_BUILDING => pht('Building'), self::STATUS_PASSED => pht('Passed'), self::STATUS_FAILED => pht('Failed'), self::STATUS_ABORTED => pht('Aborted'), self::STATUS_ERROR => pht('Unexpected Error'), self::STATUS_PAUSED => pht('Paused'), self::STATUS_DEADLOCKED => pht('Deadlocked'), ); } public static function getBuildStatusIcon($status) { switch ($status) { case self::STATUS_INACTIVE: case self::STATUS_PENDING: return PHUIStatusItemView::ICON_OPEN; case self::STATUS_BUILDING: return PHUIStatusItemView::ICON_RIGHT; case self::STATUS_PASSED: 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_PAUSED: return PHUIStatusItemView::ICON_MINUS; case self::STATUS_DEADLOCKED: return PHUIStatusItemView::ICON_WARNING; default: return PHUIStatusItemView::ICON_QUESTION; } } public static function getBuildStatusColor($status) { switch ($status) { case self::STATUS_INACTIVE: return 'dark'; case self::STATUS_PENDING: case self::STATUS_BUILDING: return 'blue'; case self::STATUS_PASSED: return 'green'; case self::STATUS_FAILED: case self::STATUS_ABORTED: case self::STATUS_ERROR: case self::STATUS_DEADLOCKED: return 'red'; case self::STATUS_PAUSED: return 'dark'; default: return 'bluegrey'; } } + public static function getWaitingStatusConstants() { + return array( + self::STATUS_INACTIVE, + self::STATUS_PENDING, + ); + } + + public static function getActiveStatusConstants() { + return array( + self::STATUS_BUILDING, + self::STATUS_PAUSED, + ); + } + + public static function getCompletedStatusConstants() { + return array( + self::STATUS_PASSED, + self::STATUS_FAILED, + self::STATUS_ABORTED, + self::STATUS_ERROR, + self::STATUS_DEADLOCKED, + ); + } + } diff --git a/src/applications/harbormaster/query/HarbormasterBuildSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildSearchEngine.php index 2999bcc6f4..0b07ca7150 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildSearchEngine.php @@ -1,126 +1,144 @@ setLabel(pht('Build Plans')) ->setKey('plans') ->setAliases(array('plan')) ->setDescription( pht('Search for builds running a given build plan.')) ->setDatasource(new HarbormasterBuildPlanDatasource()), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Statuses')) ->setKey('statuses') ->setAliases(array('status')) ->setDescription( pht('Search for builds with given statuses.')) ->setDatasource(new HarbormasterBuildStatusDatasource()), ); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); if ($map['plans']) { $query->withBuildPlanPHIDs($map['plans']); } if ($map['statuses']) { $query->withBuildStatuses($map['statuses']); } return $query; } protected function getURI($path) { return '/harbormaster/build/'.$path; } protected function getBuiltinQueryNames() { return array( 'all' => pht('All Builds'), + 'waiting' => pht('Waiting'), + 'active' => pht('Active'), + 'completed' => pht('Completed'), ); } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; + case 'waiting': + return $query + ->setParameter( + 'statuses', + HarbormasterBuildStatus::getWaitingStatusConstants()); + case 'active': + return $query + ->setParameter( + 'statuses', + HarbormasterBuildStatus::getActiveStatusConstants()); + case 'completed': + return $query + ->setParameter( + 'statuses', + HarbormasterBuildStatus::getCompletedStatusConstants()); } return parent::buildSavedQueryFromBuiltin($query_key); } protected function renderResultList( array $builds, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($builds, 'HarbormasterBuild'); $viewer = $this->requireViewer(); $buildables = mpull($builds, 'getBuildable'); $object_phids = mpull($buildables, 'getBuildablePHID'); $initiator_phids = mpull($builds, 'getInitiatorPHID'); $phids = array_mergev(array($initiator_phids, $object_phids)); $phids = array_unique(array_filter($phids)); $handles = $viewer->loadHandles($phids); $list = new PHUIObjectItemListView(); foreach ($builds as $build) { $id = $build->getID(); $initiator = $handles[$build->getInitiatorPHID()]; $buildable_object = $handles[$build->getBuildable()->getBuildablePHID()]; $item = id(new PHUIObjectItemView()) ->setViewer($viewer) ->setObject($build) ->setObjectName(pht('Build %d', $build->getID())) ->setHeader($build->getName()) ->setHref($build->getURI()) ->setEpoch($build->getDateCreated()) ->addAttribute($buildable_object->getName()); if ($initiator) { $item->addHandleIcon($initiator, $initiator->getName()); } $status = $build->getBuildStatus(); $status_icon = HarbormasterBuildStatus::getBuildStatusIcon($status); $status_color = HarbormasterBuildStatus::getBuildStatusColor($status); $status_label = HarbormasterBuildStatus::getBuildStatusName($status); $item->setStatusIcon("{$status_icon} {$status_color}", $status_label); $list->addItem($item); } $result = new PhabricatorApplicationSearchResultView(); $result->setObjectList($list); $result->setNoDataString(pht('No builds found.')); return $result; } } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 44846b9bc8..759f9b9fe0 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -1,404 +1,397 @@ setBuildStatus(HarbormasterBuildStatus::STATUS_INACTIVE) ->setBuildGeneration(0); } public function delete() { $this->openTransaction(); $this->deleteUnprocessedCommands(); $result = parent::delete(); $this->saveTransaction(); return $result; } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'buildParameters' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'buildStatus' => 'text32', 'buildGeneration' => 'uint32', 'planAutoKey' => 'text32?', 'initiatorPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_buildable' => array( 'columns' => array('buildablePHID'), ), 'key_plan' => array( 'columns' => array('buildPlanPHID'), ), 'key_status' => array( 'columns' => array('buildStatus'), ), 'key_planautokey' => array( 'columns' => array('buildablePHID', 'planAutoKey'), 'unique' => true, ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( HarbormasterBuildPHIDType::TYPECONST); } public function attachBuildable(HarbormasterBuildable $buildable) { $this->buildable = $buildable; return $this; } public function getBuildable() { return $this->assertAttached($this->buildable); } public function getName() { if ($this->getBuildPlan()) { return $this->getBuildPlan()->getName(); } return pht('Build'); } public function attachBuildPlan( HarbormasterBuildPlan $build_plan = null) { $this->buildPlan = $build_plan; return $this; } public function getBuildPlan() { return $this->assertAttached($this->buildPlan); } public function getBuildTargets() { return $this->assertAttached($this->buildTargets); } public function attachBuildTargets(array $targets) { $this->buildTargets = $targets; return $this; } public function isBuilding() { return $this->getBuildStatus() === HarbormasterBuildStatus::STATUS_PENDING || $this->getBuildStatus() === HarbormasterBuildStatus::STATUS_BUILDING; } public function isAutobuild() { return ($this->getPlanAutoKey() !== null); } public function retrieveVariablesFromBuild() { $results = array( 'buildable.diff' => null, 'buildable.revision' => null, 'buildable.commit' => null, 'repository.callsign' => null, 'repository.phid' => null, 'repository.vcs' => null, 'repository.uri' => null, 'step.timestamp' => null, 'build.id' => null, 'initiator.phid' => null, ); foreach ($this->getBuildParameters() as $key => $value) { $results['build/'.$key] = $value; } $buildable = $this->getBuildable(); $object = $buildable->getBuildableObject(); $object_variables = $object->getBuildVariables(); $results = $object_variables + $results; $results['step.timestamp'] = time(); $results['build.id'] = $this->getID(); $results['initiator.phid'] = $this->getInitiatorPHID(); return $results; } public static function getAvailableBuildVariables() { $objects = id(new PhutilClassMapQuery()) ->setAncestorClass('HarbormasterBuildableInterface') ->execute(); $variables = array(); $variables[] = array( 'step.timestamp' => pht('The current UNIX timestamp.'), 'build.id' => pht('The ID of the current build.'), 'target.phid' => pht('The PHID of the current build target.'), 'initiator.phid' => pht( 'The PHID of the user or Object that initiated the build, '. 'if applicable.'), ); foreach ($objects as $object) { $variables[] = $object->getAvailableBuildVariables(); } $variables = array_mergev($variables); return $variables; } public function isComplete() { - switch ($this->getBuildStatus()) { - case HarbormasterBuildStatus::STATUS_PASSED: - case HarbormasterBuildStatus::STATUS_FAILED: - case HarbormasterBuildStatus::STATUS_ABORTED: - case HarbormasterBuildStatus::STATUS_ERROR: - case HarbormasterBuildStatus::STATUS_PAUSED: - return true; - } - - return false; + return in_array( + $this->getBuildStatus(), + HarbormasterBuildStatus::getCompletedStatusConstants()); } public function isPaused() { return ($this->getBuildStatus() == HarbormasterBuildStatus::STATUS_PAUSED); } public function getURI() { $id = $this->getID(); return "/harbormaster/build/{$id}/"; } /* -( Build Commands )----------------------------------------------------- */ private function getUnprocessedCommands() { return $this->assertAttached($this->unprocessedCommands); } public function attachUnprocessedCommands(array $commands) { $this->unprocessedCommands = $commands; return $this; } public function canRestartBuild() { if ($this->isAutobuild()) { return false; } return !$this->isRestarting(); } public function canPauseBuild() { if ($this->isAutobuild()) { return false; } return !$this->isComplete() && !$this->isPaused() && !$this->isPausing(); } public function canAbortBuild() { if ($this->isAutobuild()) { return false; } return !$this->isComplete(); } public function canResumeBuild() { if ($this->isAutobuild()) { return false; } return $this->isPaused() && !$this->isResuming(); } public function isPausing() { $is_pausing = false; foreach ($this->getUnprocessedCommands() as $command_object) { $command = $command_object->getCommand(); switch ($command) { case HarbormasterBuildCommand::COMMAND_PAUSE: $is_pausing = true; break; case HarbormasterBuildCommand::COMMAND_RESUME: case HarbormasterBuildCommand::COMMAND_RESTART: $is_pausing = false; break; case HarbormasterBuildCommand::COMMAND_ABORT: $is_pausing = true; break; } } return $is_pausing; } public function isResuming() { $is_resuming = false; foreach ($this->getUnprocessedCommands() as $command_object) { $command = $command_object->getCommand(); switch ($command) { case HarbormasterBuildCommand::COMMAND_RESTART: case HarbormasterBuildCommand::COMMAND_RESUME: $is_resuming = true; break; case HarbormasterBuildCommand::COMMAND_PAUSE: $is_resuming = false; break; case HarbormasterBuildCommand::COMMAND_ABORT: $is_resuming = false; break; } } return $is_resuming; } public function isRestarting() { $is_restarting = false; foreach ($this->getUnprocessedCommands() as $command_object) { $command = $command_object->getCommand(); switch ($command) { case HarbormasterBuildCommand::COMMAND_RESTART: $is_restarting = true; break; } } 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(); unset($this->unprocessedCommands[$key]); } return $this; } public function canIssueCommand(PhabricatorUser $viewer, $command) { try { $this->assertCanIssueCommand($viewer, $command); return true; } catch (Exception $ex) { return false; } } public function assertCanIssueCommand(PhabricatorUser $viewer, $command) { $need_edit = false; switch ($command) { case HarbormasterBuildCommand::COMMAND_RESTART: break; case HarbormasterBuildCommand::COMMAND_PAUSE: case HarbormasterBuildCommand::COMMAND_RESUME: case HarbormasterBuildCommand::COMMAND_ABORT: $need_edit = true; break; default: throw new Exception( pht( 'Invalid Harbormaster build command "%s".', $command)); } // Issuing these commands requires that you be able to edit the build, to // prevent enemy engineers from sabotaging your builds. See T9614. if ($need_edit) { PhabricatorPolicyFilter::requireCapability( $viewer, $this->getBuildPlan(), PhabricatorPolicyCapability::CAN_EDIT); } } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new HarbormasterBuildTransactionEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new HarbormasterBuildTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return $this->getBuildable()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBuildable()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { return pht('A build inherits policies from its buildable.'); } }