diff --git a/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php b/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php index 1f6a3fceea..b0281bcb70 100644 --- a/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php +++ b/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php @@ -1,83 +1,83 @@ [1-9]\d*)' => 'HarbormasterBuildableViewController', '/harbormaster/' => array( '(?:query/(?P[^/]+)/)?' => 'HarbormasterBuildableListController', 'step/' => array( 'add/(?:(?P\d+)/)?' => 'HarbormasterStepAddController', 'edit/(?:(?P\d+)/)?' => 'HarbormasterStepEditController', 'delete/(?:(?P\d+)/)?' => 'HarbormasterStepDeleteController', ), 'build/' => array( '(?:(?P\d+)/)?' => 'HarbormasterBuildViewController', - '(?Pstop|resume|restart)/(?:(?P\d+)/)?' + '(?Pstop|resume|restart)/(?P\d+)/(?:(?P[^/]+)/)?' => 'HarbormasterBuildActionController', ), 'plan/' => array( '(?:query/(?P[^/]+)/)?' => 'HarbormasterPlanListController', 'edit/(?:(?P\d+)/)?' => 'HarbormasterPlanEditController', 'order/(?:(?P\d+)/)?' => 'HarbormasterPlanOrderController', 'disable/(?P\d+)/' => 'HarbormasterPlanDisableController', 'run/(?P\d+)/' => 'HarbormasterPlanRunController', '(?P\d+)/' => 'HarbormasterPlanViewController', ), ), ); } public function getCustomCapabilities() { return array( HarbormasterCapabilityManagePlans::CAPABILITY => array( 'caption' => pht('Can create and manage build plans.'), 'default' => PhabricatorPolicies::POLICY_ADMIN, ), ); } } diff --git a/src/applications/harbormaster/controller/HarbormasterBuildActionController.php b/src/applications/harbormaster/controller/HarbormasterBuildActionController.php index 5faf35a201..d9f42be0b0 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildActionController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildActionController.php @@ -1,146 +1,155 @@ id = $data['id']; $this->action = $data['action']; + $this->via = idx($data, 'via'); } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $command = $this->action; $build = id(new HarbormasterBuildQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->executeOne(); if (!$build) { return new Aphront404Response(); } switch ($command) { case HarbormasterBuildCommand::COMMAND_RESTART: $can_issue = $build->canRestartBuild(); break; case HarbormasterBuildCommand::COMMAND_STOP: $can_issue = $build->canStopBuild(); break; case HarbormasterBuildCommand::COMMAND_RESUME: $can_issue = $build->canResumeBuild(); break; default: return new Aphront400Response(); } - $build_uri = $this->getApplicationURI('/build/'.$build->getID().'/'); + switch ($this->via) { + case 'buildable': + $return_uri = $build->getBuildable()->getMonogram(); + break; + default: + $return_uri = $this->getApplicationURI('/build/'.$build->getID().'/'); + break; + } if ($request->isDialogFormPost() && $can_issue) { // Issue the new build command. id(new HarbormasterBuildCommand()) ->setAuthorPHID($viewer->getPHID()) ->setTargetPHID($build->getPHID()) ->setCommand($command) ->save(); // Schedule a build update. We may already have stuff in queue (in which // case this will just no-op), but we might also be dealing with a // stopped build, which won't restart unless we deal with this. PhabricatorWorker::scheduleTask( 'HarbormasterBuildWorker', array( 'buildID' => $build->getID() )); - return id(new AphrontRedirectResponse())->setURI($build_uri); + return id(new AphrontRedirectResponse())->setURI($return_uri); } switch ($command) { case HarbormasterBuildCommand::COMMAND_RESTART: if ($can_issue) { $title = pht('Really restart build?'); $body = pht( 'Progress on this build will be discarded and the build will '. 'restart. Side effects of the build will occur again. Really '. 'restart build?'); $submit = pht('Restart 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.'); } } break; case HarbormasterBuildCommand::COMMAND_STOP: if ($can_issue) { $title = pht('Really stop build?'); $body = pht( 'If you stop this build, work will halt once the current steps '. 'complete. You can resume the build later.'); $submit = pht('Stop Build'); } else { $title = pht('Unable to Stop Build'); if ($build->isComplete()) { $body = pht( 'This build is already complete. You can not stop a completed '. 'build.'); } else if ($build->isStopped()) { $body = pht( 'This build is already stopped. You can not stop a build which '. 'has already been stopped.'); } else if ($build->isStopping()) { $body = pht( 'This build is already stopping. You can not reissue a stop '. 'command to a stopping build.'); } else { $body = pht( 'This build can not be stopped.'); } } break; case HarbormasterBuildCommand::COMMAND_RESUME: if ($can_issue) { $title = pht('Really resume build?'); $body = pht( 'Work will continue on the build. Really resume?'); $submit = pht('Resume Build'); } else { $title = pht('Unable to Resume Build'); if ($build->isResuming()) { $body = pht( 'This build is already resuming. You can not reissue a resume '. 'command to a resuming build.'); } else if (!$build->isStopped()) { $body = pht( 'This build is not stopped. You can only resume a stopped '. 'build.'); } } break; } $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setTitle($title) ->appendChild($body) - ->addCancelButton($build_uri); + ->addCancelButton($return_uri); if ($can_issue) { $dialog->addSubmitButton($submit); } return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index af691a0b38..ccbda9acc4 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -1,308 +1,319 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $id = $this->id; $build = id(new HarbormasterBuildQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$build) { return new Aphront404Response(); } $title = pht("Build %d", $id); $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) ->setPolicyObject($build); + if ($build->isRestarting()) { + $header->setStatus('warning', 'red', pht('Restarting')); + } else if ($build->isStopping()) { + $header->setStatus('warning', 'red', pht('Stopping')); + } else if ($build->isResuming()) { + $header->setStatus('warning', 'red', pht('Resuming')); + } + $box = id(new PHUIObjectBoxView()) ->setHeader($header); $actions = $this->buildActionList($build); $this->buildPropertyLists($box, $build, $actions); $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + $build->getBuildable()->getMonogram(), + '/'.$build->getBuildable()->getMonogram()); $crumbs->addTextCrumb($title); $build_targets = id(new HarbormasterBuildTargetQuery()) ->setViewer($viewer) ->withBuildPHIDs(array($build->getPHID())) ->execute(); $targets = array(); foreach ($build_targets as $build_target) { $header = id(new PHUIHeaderView()) ->setHeader(pht( 'Build Target %d (%s)', $build_target->getID(), $build_target->getImplementation()->getName())) ->setUser($viewer); $properties = new PHUIPropertyListView(); $details = $build_target->getDetails(); if ($details) { $properties->addSectionHeader(pht('Configuration Details')); foreach ($details as $key => $value) { $properties->addProperty($key, $value); } } $variables = $build_target->getVariables(); if ($variables) { $properties->addSectionHeader(pht('Variables')); foreach ($variables as $key => $value) { $properties->addProperty($key, $value); } } $targets[] = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); $targets[] = $this->buildArtifacts($build_target); $targets[] = $this->buildLog($build, $build_target); } return $this->buildApplicationPage( array( $crumbs, $box, $targets ), array( 'title' => $title, 'device' => true, )); } private function buildArtifacts(HarbormasterBuildTarget $build_target) { $request = $this->getRequest(); $viewer = $request->getUser(); $artifacts = id(new HarbormasterBuildArtifactQuery()) ->setViewer($viewer) ->withBuildTargetPHIDs(array($build_target->getPHID())) ->execute(); if (count($artifacts) === 0) { return null; } $list = new PHUIObjectItemListView(); foreach ($artifacts as $artifact) { $list->addItem($artifact->getObjectItemView($viewer)); } $header = id(new PHUIHeaderView()) ->setHeader(pht('Build Artifacts')) ->setUser($viewer); $box = id(new PHUIObjectBoxView()) ->setHeader($header); return array($box, $list); } private function buildLog( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { $request = $this->getRequest(); $viewer = $request->getUser(); $limit = $request->getInt('l', 25); $logs = id(new HarbormasterBuildLogQuery()) ->setViewer($viewer) ->withBuildTargetPHIDs(array($build_target->getPHID())) ->execute(); $log_boxes = array(); foreach ($logs as $log) { $start = 1; $lines = preg_split("/\r\n|\r|\n/", $log->getLogText()); if ($limit !== 0) { $start = count($lines) - $limit; if ($start >= 1) { $lines = array_slice($lines, -$limit, $limit); } else { $start = 1; } } $log_view = new ShellLogView(); $log_view->setLines($lines); $log_view->setStart($start); $header = id(new PHUIHeaderView()) ->setHeader(pht( 'Build Log %d (%s - %s)', $log->getID(), $log->getLogSource(), $log->getLogType())) ->setSubheader($this->createLogHeader($build, $log)) ->setUser($viewer); $log_boxes[] = id(new PHUIObjectBoxView()) ->setHeader($header) ->setForm($log_view); } return $log_boxes; } private function createLogHeader($build, $log) { $request = $this->getRequest(); $limit = $request->getInt('l', 25); $lines_25 = $this->getApplicationURI('/build/'.$build->getID().'/?l=25'); $lines_50 = $this->getApplicationURI('/build/'.$build->getID().'/?l=50'); $lines_100 = $this->getApplicationURI('/build/'.$build->getID().'/?l=100'); $lines_0 = $this->getApplicationURI('/build/'.$build->getID().'/?l=0'); $link_25 = phutil_tag('a', array('href' => $lines_25), pht('25')); $link_50 = phutil_tag('a', array('href' => $lines_50), pht('50')); $link_100 = phutil_tag('a', array('href' => $lines_100), pht('100')); $link_0 = phutil_tag('a', array('href' => $lines_0), pht('Unlimited')); if ($limit === 25) { $link_25 = phutil_tag('strong', array(), $link_25); } else if ($limit === 50) { $link_50 = phutil_tag('strong', array(), $link_50); } else if ($limit === 100) { $link_100 = phutil_tag('strong', array(), $link_100); } else if ($limit === 0) { $link_0 = phutil_tag('strong', array(), $link_0); } return phutil_tag( 'span', array(), array( $link_25, ' - ', $link_50, ' - ', $link_100, ' - ', $link_0, ' Lines')); } private function buildActionList(HarbormasterBuild $build) { $request = $this->getRequest(); $viewer = $request->getUser(); $id = $build->getID(); $list = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($build) ->setObjectURI("/build/{$id}"); $can_restart = $build->canRestartBuild(); $can_stop = $build->canStopBuild(); $can_resume = $build->canResumeBuild(); $list->addAction( id(new PhabricatorActionView()) ->setName(pht('Restart Build')) ->setIcon('backward') ->setHref($this->getApplicationURI('/build/restart/'.$id.'/')) ->setDisabled(!$can_restart) ->setWorkflow(true)); $list->addAction( id(new PhabricatorActionView()) ->setName(pht('Stop Build')) ->setIcon('stop') ->setHref($this->getApplicationURI('/build/stop/'.$id.'/')) ->setDisabled(!$can_stop) ->setWorkflow(true)); $list->addAction( id(new PhabricatorActionView()) ->setName(pht('Resume Build')) ->setIcon('play') ->setHref($this->getApplicationURI('/build/resume/'.$id.'/')) ->setDisabled(!$can_resume) ->setWorkflow(true)); return $list; } private function buildPropertyLists( PHUIObjectBoxView $box, HarbormasterBuild $build, PhabricatorActionListView $actions) { $request = $this->getRequest(); $viewer = $request->getUser(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($build) ->setActionList($actions); $box->addPropertyList($properties); $properties->addProperty( pht('Status'), $this->getStatus($build)); $handles = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs(array( $build->getBuildablePHID(), $build->getBuildPlanPHID())) ->execute(); $properties->addProperty( pht('Buildable'), $handles[$build->getBuildablePHID()]->renderLink()); $properties->addProperty( pht('Build Plan'), $handles[$build->getBuildPlanPHID()]->renderLink()); } private function getStatus(HarbormasterBuild $build) { if ($build->isStopping()) { return pht('Stopping'); } switch ($build->getBuildStatus()) { case HarbormasterBuild::STATUS_INACTIVE: return pht('Inactive'); case HarbormasterBuild::STATUS_PENDING: return pht('Pending'); case HarbormasterBuild::STATUS_WAITING: return pht('Waiting'); case HarbormasterBuild::STATUS_BUILDING: return pht('Building'); case HarbormasterBuild::STATUS_PASSED: return pht('Passed'); case HarbormasterBuild::STATUS_FAILED: return pht('Failed'); case HarbormasterBuild::STATUS_ERROR: return pht('Unexpected Error'); case HarbormasterBuild::STATUS_STOPPED: return pht('Stopped'); default: return pht('Unknown'); } } } diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php index 0075875b78..76da00406e 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -1,155 +1,191 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $id = $this->id; $buildable = id(new HarbormasterBuildableQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needBuildableHandles(true) ->needContainerHandles(true) ->executeOne(); if (!$buildable) { return new Aphront404Response(); } $builds = id(new HarbormasterBuildQuery()) ->setViewer($viewer) ->withBuildablePHIDs(array($buildable->getPHID())) ->execute(); $build_list = id(new PHUIObjectItemListView()) ->setUser($viewer); foreach ($builds as $build) { $view_uri = $this->getApplicationURI('/build/'.$build->getID().'/'); $item = id(new PHUIObjectItemView()) ->setObjectName(pht('Build %d', $build->getID())) ->setHeader($build->getName()) ->setHref($view_uri); - if ($build->isStopping()) { - $item->setBarColor('black'); - $item->addAttribute(pht('Stopping')); + + switch ($build->getBuildStatus()) { + case HarbormasterBuild::STATUS_INACTIVE: + $item->setBarColor('grey'); + $item->addAttribute(pht('Inactive')); + break; + case HarbormasterBuild::STATUS_PENDING: + $item->setBarColor('blue'); + $item->addAttribute(pht('Pending')); + break; + case HarbormasterBuild::STATUS_WAITING: + $item->setBarColor('violet'); + $item->addAttribute(pht('Waiting')); + break; + case HarbormasterBuild::STATUS_BUILDING: + $item->setBarColor('yellow'); + $item->addAttribute(pht('Building')); + break; + case HarbormasterBuild::STATUS_PASSED: + $item->setBarColor('green'); + $item->addAttribute(pht('Passed')); + break; + case HarbormasterBuild::STATUS_FAILED: + $item->setBarColor('red'); + $item->addAttribute(pht('Failed')); + break; + case HarbormasterBuild::STATUS_ERROR: + $item->setBarColor('red'); + $item->addAttribute(pht('Unexpected Error')); + break; + case HarbormasterBuild::STATUS_STOPPED: + $item->setBarColor('black'); + $item->addAttribute(pht('Stopped')); + break; + } + + if ($build->isRestarting()) { + $item->addIcon('backward', pht('Restarting')); + } else if ($build->isStopping()) { + $item->addIcon('stop', pht('Stopping')); + } else if ($build->isResuming()) { + $item->addIcon('play', pht('Resuming')); + } + + $build_id = $build->getID(); + + $restart_uri = "build/restart/{$build_id}/buildable/"; + $resume_uri = "build/resume/{$build_id}/buildable/"; + $stop_uri = "build/stop/{$build_id}/buildable/"; + + $item->addAction( + id(new PHUIListItemView()) + ->setIcon('backward') + ->setName(pht('Restart')) + ->setHref($this->getApplicationURI($restart_uri)) + ->setWorkflow(true) + ->setDisabled(!$build->canRestartBuild())); + + if ($build->canResumeBuild()) { + $item->addAction( + id(new PHUIListItemView()) + ->setIcon('play') + ->setName(pht('Resume')) + ->setHref($this->getApplicationURI($resume_uri)) + ->setWorkflow(true)); } else { - switch ($build->getBuildStatus()) { - case HarbormasterBuild::STATUS_INACTIVE: - $item->setBarColor('grey'); - $item->addAttribute(pht('Inactive')); - break; - case HarbormasterBuild::STATUS_PENDING: - $item->setBarColor('blue'); - $item->addAttribute(pht('Pending')); - break; - case HarbormasterBuild::STATUS_WAITING: - $item->setBarColor('violet'); - $item->addAttribute(pht('Waiting')); - break; - case HarbormasterBuild::STATUS_BUILDING: - $item->setBarColor('yellow'); - $item->addAttribute(pht('Building')); - break; - case HarbormasterBuild::STATUS_PASSED: - $item->setBarColor('green'); - $item->addAttribute(pht('Passed')); - break; - case HarbormasterBuild::STATUS_FAILED: - $item->setBarColor('red'); - $item->addAttribute(pht('Failed')); - break; - case HarbormasterBuild::STATUS_ERROR: - $item->setBarColor('red'); - $item->addAttribute(pht('Unexpected Error')); - break; - case HarbormasterBuild::STATUS_STOPPED: - $item->setBarColor('black'); - $item->addAttribute(pht('Stopped')); - break; - } + $item->addAction( + id(new PHUIListItemView()) + ->setIcon('stop') + ->setName(pht('Stop')) + ->setHref($this->getApplicationURI($stop_uri)) + ->setWorkflow(true) + ->setDisabled(!$build->canStopBuild())); } + $build_list->addItem($item); } $title = pht("Buildable %d", $id); $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) ->setPolicyObject($buildable); $box = id(new PHUIObjectBoxView()) ->setHeader($header); $actions = $this->buildActionList($buildable); $this->buildPropertyLists($box, $buildable, $actions); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb("B{$id}"); return $this->buildApplicationPage( array( $crumbs, $box, $build_list, ), array( 'title' => $title, 'device' => true, )); } private function buildActionList(HarbormasterBuildable $buildable) { $request = $this->getRequest(); $viewer = $request->getUser(); $id = $buildable->getID(); $list = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($buildable) ->setObjectURI("/B{$id}"); return $list; } private function buildPropertyLists( PHUIObjectBoxView $box, HarbormasterBuildable $buildable, PhabricatorActionListView $actions) { $request = $this->getRequest(); $viewer = $request->getUser(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($buildable) ->setActionList($actions); $box->addPropertyList($properties); $properties->addProperty( pht('Buildable'), $buildable->getBuildableHandle()->renderLink()); if ($buildable->getContainerHandle() !== null) { $properties->addProperty( pht('Container'), $buildable->getContainerHandle()->renderLink()); } $properties->addProperty( pht('Origin'), $buildable->getIsManualBuildable() ? pht('Manual Buildable') : pht('Automatic Buildable')); } } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index 2c3f1439eb..41ec1c9303 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -1,212 +1,216 @@ setIsManualBuildable(0) ->setBuildStatus(self::STATUS_WHATEVER) ->setBuildableStatus(self::STATUS_WHATEVER); } + public function getMonogram() { + return 'B'.$this->getID(); + } + /** * Returns an existing buildable for the object's PHID or creates a * new buildable implicitly if needed. */ public static function createOrLoadExisting( PhabricatorUser $actor, $buildable_object_phid, $container_object_phid) { $buildable = id(new HarbormasterBuildableQuery()) ->setViewer($actor) ->withBuildablePHIDs(array($buildable_object_phid)) ->withManualBuildables(false) ->setLimit(1) ->executeOne(); if ($buildable) { return $buildable; } $buildable = HarbormasterBuildable::initializeNewBuildable($actor) ->setBuildablePHID($buildable_object_phid) ->setContainerPHID($container_object_phid); $buildable->save(); return $buildable; } /** * Looks up the plan PHIDs and applies the plans to the specified * object identified by it's PHID. */ public static function applyBuildPlans( $phid, $container_phid, array $plan_phids) { if (count($plan_phids) === 0) { return; } // Skip all of this logic if the Harbormaster application // isn't currently installed. $harbormaster_app = 'PhabricatorApplicationHarbormaster'; if (!PhabricatorApplication::isClassInstalled($harbormaster_app)) { return; } $buildable = HarbormasterBuildable::createOrLoadExisting( PhabricatorUser::getOmnipotentUser(), $phid, $container_phid); $plans = id(new HarbormasterBuildPlanQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs($plan_phids) ->execute(); foreach ($plans as $plan) { if ($plan->isDisabled()) { // TODO: This should be communicated more clearly -- maybe we should // create the build but set the status to "disabled" or "derelict". continue; } $buildable->applyPlan($plan); } } public function applyPlan(HarbormasterBuildPlan $plan) { $viewer = PhabricatorUser::getOmnipotentUser(); $build = HarbormasterBuild::initializeNewBuild($viewer) ->setBuildablePHID($this->getPHID()) ->setBuildPlanPHID($plan->getPHID()) ->setBuildStatus(HarbormasterBuild::STATUS_PENDING) ->save(); PhabricatorWorker::scheduleTask( 'HarbormasterBuildWorker', array( 'buildID' => $build->getID() )); return $this; } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( HarbormasterPHIDTypeBuildable::TYPECONST); } public function attachBuildableObject($buildable_object) { $this->buildableObject = $buildable_object; return $this; } public function getBuildableObject() { return $this->assertAttached($this->buildableObject); } public function attachContainerObject($container_object) { $this->containerObject = $container_object; return $this; } public function getContainerObject() { return $this->assertAttached($this->containerObject); } public function attachContainerHandle($container_handle) { $this->containerHandle = $container_handle; return $this; } public function getContainerHandle() { return $this->assertAttached($this->containerHandle); } public function attachBuildableHandle($buildable_handle) { $this->buildableHandle = $buildable_handle; return $this; } public function getBuildableHandle() { return $this->assertAttached($this->buildableHandle); } public function attachBuilds(array $builds) { assert_instances_of($builds, 'HarbormasterBuild'); $this->builds = $builds; return $this; } public function getBuilds() { return $this->assertAttached($this->builds); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { return $this->getBuildableObject()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBuildableObject()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { return pht( 'Users must be able to see the revision or repository to see a '. 'buildable.'); } /* -( HarbormasterBuildableInterface )------------------------------------- */ public function getHarbormasterBuildablePHID() { // NOTE: This is essentially just for convenience, as it allows you create // a copy of a buildable by specifying `B123` without bothering to go // look up the underlying object. return $this->getBuildablePHID(); } public function getHarbormasterContainerPHID() { return $this->getContainerPHID(); } }