Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -728,6 +728,7 @@ 'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php', 'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php', 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', + 'HarbormasterBuildableActionController' => 'applications/harbormaster/controller/HarbormasterBuildableActionController.php', 'HarbormasterBuildableInterface' => 'applications/harbormaster/interface/HarbormasterBuildableInterface.php', 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', 'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php', @@ -3206,6 +3207,7 @@ 1 => 'PhabricatorPolicyInterface', 2 => 'HarbormasterBuildableInterface', ), + 'HarbormasterBuildableActionController' => 'HarbormasterController', 'HarbormasterBuildableListController' => array( 0 => 'HarbormasterController', Index: src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php =================================================================== --- src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php +++ src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php @@ -53,6 +53,10 @@ 'edit/(?:(?P\d+)/)?' => 'HarbormasterStepEditController', 'delete/(?:(?P\d+)/)?' => 'HarbormasterStepDeleteController', ), + 'buildable/' => array( + '(?P\d+)/(?Pstop|resume|restart)/' + => 'HarbormasterBuildableActionController', + ), 'build/' => array( '(?:(?P\d+)/)?' => 'HarbormasterBuildViewController', '(?Pstop|resume|restart)/(?P\d+)/(?:(?P[^/]+)/)?' Index: src/applications/harbormaster/controller/HarbormasterBuildActionController.php =================================================================== --- src/applications/harbormaster/controller/HarbormasterBuildActionController.php +++ src/applications/harbormaster/controller/HarbormasterBuildActionController.php @@ -21,6 +21,11 @@ $build = id(new HarbormasterBuildQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) ->executeOne(); if (!$build) { return new Aphront404Response(); @@ -42,7 +47,7 @@ switch ($this->via) { case 'buildable': - $return_uri = $build->getBuildable()->getMonogram(); + $return_uri = '/'.$build->getBuildable()->getMonogram(); break; default: $return_uri = $this->getApplicationURI('/build/'.$build->getID().'/'); Index: src/applications/harbormaster/controller/HarbormasterBuildableActionController.php =================================================================== --- /dev/null +++ src/applications/harbormaster/controller/HarbormasterBuildableActionController.php @@ -0,0 +1,127 @@ +id = $data['id']; + $this->action = $data['action']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + $command = $this->action; + + $buildable = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->needBuilds(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$buildable) { + return new Aphront404Response(); + } + + $issuable = array(); + + foreach ($buildable->getBuilds() as $build) { + switch ($command) { + case HarbormasterBuildCommand::COMMAND_RESTART: + if ($build->canRestartBuild()) { + $issuable[] = $build; + } + break; + case HarbormasterBuildCommand::COMMAND_STOP: + if ($build->canStopBuild()) { + $issuable[] = $build; + } + break; + case HarbormasterBuildCommand::COMMAND_RESUME: + if ($build->canResumeBuild()) { + $issuable[] = $build; + } + break; + default: + return new Aphront400Response(); + } + } + + $return_uri = $buildable->getMonogram(); + if ($request->isDialogFormPost() && $issuable) { + foreach ($issuable as $build) { + id(new HarbormasterBuildCommand()) + ->setAuthorPHID($viewer->getPHID()) + ->setTargetPHID($build->getPHID()) + ->setCommand($command) + ->save(); + + PhabricatorWorker::scheduleTask( + 'HarbormasterBuildWorker', + array( + 'buildID' => $build->getID() + )); + } + + return id(new AphrontRedirectResponse())->setURI($return_uri); + } + + switch ($command) { + case HarbormasterBuildCommand::COMMAND_RESTART: + if ($issuable) { + $title = pht('Really restart all builds?'); + $body = pht( + 'Progress on all builds will be discarded, and all builds will '. + 'restart. Side effects of the builds will occur again. Really '. + 'restart all builds?'); + $submit = pht('Restart All Builds'); + } else { + $title = pht('Unable to Restart Build'); + $body = pht('No builds can be restarted.'); + } + break; + case HarbormasterBuildCommand::COMMAND_STOP: + if ($issuable) { + $title = pht('Really stop all builds?'); + $body = pht( + 'If you stop all build, work will halt once the current steps '. + 'complete. You can resume the builds later.'); + $submit = pht('Stop All Builds'); + } else { + $title = pht('Unable to Stop Build'); + $body = pht('No builds can be stopped.'); + } + break; + case HarbormasterBuildCommand::COMMAND_RESUME: + if ($issuable) { + $title = pht('Really resume all builds?'); + $body = pht('Work will continue on all builds. Really resume?'); + $submit = pht('Resume All Builds'); + } else { + $title = pht('Unable to Resume Build'); + $body = pht('No builds can be resumed.'); + } + break; + } + + $dialog = id(new AphrontDialogView()) + ->setUser($viewer) + ->setTitle($title) + ->appendChild($body) + ->addCancelButton($return_uri); + + if ($issuable) { + $dialog->addSubmitButton($submit); + } + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + +} Index: src/applications/harbormaster/controller/HarbormasterBuildableViewController.php =================================================================== --- src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -20,19 +20,15 @@ ->withIDs(array($id)) ->needBuildableHandles(true) ->needContainerHandles(true) + ->needBuilds(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) { + foreach ($buildable->getBuilds() as $build) { $view_uri = $this->getApplicationURI('/build/'.$build->getID().'/'); $item = id(new PHUIObjectItemView()) ->setObjectName(pht('Build %d', $build->getID())) @@ -152,7 +148,56 @@ $list = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($buildable) - ->setObjectURI("/B{$id}"); + ->setObjectURI($buildable->getMonogram()); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $buildable, + PhabricatorPolicyCapability::CAN_EDIT); + + $can_restart = false; + $can_resume = false; + $can_stop = false; + + foreach ($buildable->getBuilds() as $build) { + if ($build->canRestartBuild()) { + $can_restart = true; + } + if ($build->canResumeBuild()) { + $can_resume = true; + } + if ($build->canStopBuild()) { + $can_stop = true; + } + } + + $restart_uri = "buildable/{$id}/restart/"; + $stop_uri = "buildable/{$id}/stop/"; + $resume_uri = "buildable/{$id}/resume/"; + + $list->addAction( + id(new PhabricatorActionView()) + ->setIcon('backward') + ->setName(pht('Restart All Builds')) + ->setHref($this->getApplicationURI($restart_uri)) + ->setWorkflow(true) + ->setDisabled(!$can_restart || !$can_edit)); + + $list->addAction( + id(new PhabricatorActionView()) + ->setIcon('stop') + ->setName(pht('Stop All Builds')) + ->setHref($this->getApplicationURI($stop_uri)) + ->setWorkflow(true) + ->setDisabled(!$can_stop || !$can_edit)); + + $list->addAction( + id(new PhabricatorActionView()) + ->setIcon('play') + ->setName(pht('Resume All Builds')) + ->setHref($this->getApplicationURI($resume_uri)) + ->setWorkflow(true) + ->setDisabled(!$can_resume || !$can_edit)); return $list; } Index: src/applications/harbormaster/storage/HarbormasterBuildable.php =================================================================== --- src/applications/harbormaster/storage/HarbormasterBuildable.php +++ src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -177,6 +177,7 @@ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } @@ -191,9 +192,7 @@ } public function describeAutomaticCapability($capability) { - return pht( - 'Users must be able to see the revision or repository to see a '. - 'buildable.'); + return pht('A buildable inherits policies from the underlying object.'); } Index: src/applications/harbormaster/storage/build/HarbormasterBuild.php =================================================================== --- src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -311,6 +311,7 @@ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } @@ -325,8 +326,7 @@ } public function describeAutomaticCapability($capability) { - return pht( - 'Users must be able to see a buildable to view its build plans.'); + return pht('A build inherits policies from its buildable.'); } }