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 @@ -951,6 +951,7 @@ 'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php', 'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php', 'HarbormasterBuildStepEditor' => 'applications/harbormaster/editor/HarbormasterBuildStepEditor.php', + 'HarbormasterBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php', 'HarbormasterBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterBuildStepImplementation.php', 'HarbormasterBuildStepImplementationTestCase' => 'applications/harbormaster/step/__tests__/HarbormasterBuildStepImplementationTestCase.php', 'HarbormasterBuildStepPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php', @@ -978,10 +979,12 @@ 'HarbormasterBuildableTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php', 'HarbormasterBuildableTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildableTransactionQuery.php', 'HarbormasterBuildableViewController' => 'applications/harbormaster/controller/HarbormasterBuildableViewController.php', + 'HarbormasterBuiltinBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php', 'HarbormasterCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php', 'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php', 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', + 'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php', 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', 'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php', 'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php', @@ -992,12 +995,14 @@ 'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php', 'HarbormasterMessageType' => 'applications/harbormaster/engine/HarbormasterMessageType.php', 'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php', + 'HarbormasterOtherBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterOtherBuildStepGroup.php', 'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php', 'HarbormasterPlanDisableController' => 'applications/harbormaster/controller/HarbormasterPlanDisableController.php', 'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php', 'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php', 'HarbormasterPlanRunController' => 'applications/harbormaster/controller/HarbormasterPlanRunController.php', 'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php', + 'HarbormasterPrototypeBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterPrototypeBuildStepGroup.php', 'HarbormasterPublishFragmentBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php', 'HarbormasterQueryAutotargetsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryAutotargetsConduitAPIMethod.php', 'HarbormasterQueryBuildablesConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php', @@ -1013,6 +1018,7 @@ 'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php', 'HarbormasterTargetEngine' => 'applications/harbormaster/engine/HarbormasterTargetEngine.php', 'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php', + 'HarbormasterTestBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterTestBuildStepGroup.php', 'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php', 'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php', 'HarbormasterUnitMessagesController' => 'applications/harbormaster/controller/HarbormasterUnitMessagesController.php', @@ -4660,6 +4666,7 @@ ), 'HarbormasterBuildStepCustomField' => 'PhabricatorCustomField', 'HarbormasterBuildStepEditor' => 'PhabricatorApplicationTransactionEditor', + 'HarbormasterBuildStepGroup' => 'Phobject', 'HarbormasterBuildStepImplementation' => 'Phobject', 'HarbormasterBuildStepImplementationTestCase' => 'PhabricatorTestCase', 'HarbormasterBuildStepPHIDType' => 'PhabricatorPHIDType', @@ -4693,10 +4700,12 @@ 'HarbormasterBuildableTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'HarbormasterBuildableTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'HarbormasterBuildableViewController' => 'HarbormasterController', + 'HarbormasterBuiltinBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod', 'HarbormasterController' => 'PhabricatorController', 'HarbormasterDAO' => 'PhabricatorLiskDAO', + 'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterLintMessagesController' => 'HarbormasterController', @@ -4707,12 +4716,14 @@ 'HarbormasterManagementWorkflow' => 'PhabricatorManagementWorkflow', 'HarbormasterMessageType' => 'Phobject', 'HarbormasterObject' => 'HarbormasterDAO', + 'HarbormasterOtherBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterPlanController' => 'HarbormasterController', 'HarbormasterPlanDisableController' => 'HarbormasterPlanController', 'HarbormasterPlanEditController' => 'HarbormasterPlanController', 'HarbormasterPlanListController' => 'HarbormasterPlanController', 'HarbormasterPlanRunController' => 'HarbormasterController', 'HarbormasterPlanViewController' => 'HarbormasterPlanController', + 'HarbormasterPrototypeBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterPublishFragmentBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterQueryAutotargetsConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterQueryBuildablesConduitAPIMethod' => 'HarbormasterConduitAPIMethod', @@ -4728,6 +4739,7 @@ 'HarbormasterStepEditController' => 'HarbormasterController', 'HarbormasterTargetEngine' => 'Phobject', 'HarbormasterTargetWorker' => 'HarbormasterWorker', + 'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation', 'HarbormasterUIEventListener' => 'PhabricatorEventListener', 'HarbormasterUnitMessagesController' => 'HarbormasterController', diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php --- a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php @@ -235,7 +235,7 @@ id(new PHUIIconView()) ->setIconFont('fa-plus')) ->setDisabled(!$can_edit) - ->setWorkflow(true)); + ->setWorkflow(!$can_edit)); $step_box = id(new PHUIObjectBoxView()) ->setHeader($header) diff --git a/src/applications/harbormaster/controller/HarbormasterStepAddController.php b/src/applications/harbormaster/controller/HarbormasterStepAddController.php --- a/src/applications/harbormaster/controller/HarbormasterStepAddController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepAddController.php @@ -23,48 +23,83 @@ $plan_id = $plan->getID(); $cancel_uri = $this->getApplicationURI("plan/{$plan_id}/"); + $plan_title = pht('Plan %d', $plan_id); $all = HarbormasterBuildStepImplementation::getImplementations(); - foreach ($all as $key => $impl) { - if ($impl->shouldRequireAutotargeting()) { - unset($all[$key]); + $all = msort($all, 'getName'); + + $all_groups = HarbormasterBuildStepGroup::getAllGroups(); + foreach ($all as $impl) { + $group_key = $impl->getBuildStepGroupKey(); + if (empty($all_groups[$group_key])) { + throw new Exception( + pht( + 'Build step "%s" has step group key "%s", but no step group '. + 'with that key exists.', + get_class($impl), + $group_key)); } } - $errors = array(); - if ($request->isFormPost()) { - $class = $request->getStr('class'); - if (empty($all[$class])) { - $errors[] = pht('Choose the type of build step you want to add.'); + $groups = mgroup($all, 'getBuildStepGroupKey'); + $lists = array(); + + $enabled_groups = HarbormasterBuildStepGroup::getAllEnabledGroups(); + foreach ($enabled_groups as $group) { + $list = id(new PHUIObjectItemListView()) + ->setHeader($group->getGroupName()) + ->setNoDataString( + pht( + 'This group has no available build steps.')); + + $steps = idx($groups, $group->getGroupKey(), array()); + + foreach ($steps as $key => $impl) { + if ($impl->shouldRequireAutotargeting()) { + unset($steps[$key]); + continue; + } } - if (!$errors) { - $new_uri = $this->getApplicationURI("step/new/{$plan_id}/{$class}/"); - return id(new AphrontRedirectResponse())->setURI($new_uri); + + if (!$steps && !$group->shouldShowIfEmpty()) { + continue; } - } - $control = id(new AphrontFormRadioButtonControl()) - ->setName('class'); + foreach ($steps as $key => $impl) { + $class = get_class($impl); - foreach ($all as $class => $implementation) { - $control->addButton( - $class, - $implementation->getName(), - $implementation->getGenericDescription()); - } + $new_uri = $this->getApplicationURI("step/new/{$plan_id}/{$class}/"); - if ($errors) { - $errors = id(new PHUIInfoView()) - ->setErrors($errors); + $item = id(new PHUIObjectItemView()) + ->setHeader($impl->getName()) + ->setHref($new_uri) + ->addAttribute($impl->getGenericDescription()); + + $list->addItem($item); + } + + $lists[] = $list; } - return $this->newDialog() - ->setTitle(pht('Add New Step')) - ->addSubmitButton(pht('Add Build Step')) - ->addCancelButton($cancel_uri) - ->appendChild($errors) - ->appendParagraph(pht('Choose a type of build step to add:')) - ->appendChild($control); + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($plan_title, $cancel_uri) + ->addTextCrumb(pht('Add Build Step')); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Add Build Step')) + ->appendChild($lists); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => array( + $plan_title, + pht('Add Build Step'), + ), + )); } } diff --git a/src/applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php @@ -25,6 +25,10 @@ return pht('Automatic `arc lint` step.'); } + public function getBuildStepGroupKey() { + return HarbormasterBuiltinBuildStepGroup::GROUPKEY; + } + public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { diff --git a/src/applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php @@ -25,6 +25,10 @@ return pht('Automatic `arc unit` step.'); } + public function getBuildStepGroupKey() { + return HarbormasterBuiltinBuildStepGroup::GROUPKEY; + } + public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -41,6 +41,10 @@ */ abstract public function getName(); + public function getBuildStepGroupKey() { + return HarbormasterOtherBuildStepGroup::GROUPKEY; + } + /** * The generic description of the implementation. */ diff --git a/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php @@ -13,6 +13,10 @@ return pht('Run a command on Drydock host.'); } + public function getBuildStepGroupKey() { + return HarbormasterPrototypeBuildStepGroup::GROUPKEY; + } + public function getDescription() { return pht( 'Run command %s on host %s.', diff --git a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php @@ -11,6 +11,10 @@ return pht('Make an HTTP request.'); } + public function getBuildStepGroupKey() { + return HarbormasterExternalBuildStepGroup::GROUPKEY; + } + public function getDescription() { $domain = null; $uri = $this->getSetting('uri'); diff --git a/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php @@ -11,6 +11,10 @@ return pht('Obtain a lease on a Drydock host for performing builds.'); } + public function getBuildStepGroupKey() { + return HarbormasterPrototypeBuildStepGroup::GROUPKEY; + } + public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { diff --git a/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php @@ -11,6 +11,11 @@ return pht('Publish a fragment based on a file artifact.'); } + + public function getBuildStepGroupKey() { + return HarbormasterPrototypeBuildStepGroup::GROUPKEY; + } + public function getDescription() { return pht( 'Publish file artifact %s as fragment %s.', diff --git a/src/applications/harbormaster/step/HarbormasterSleepBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterSleepBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterSleepBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterSleepBuildStepImplementation.php @@ -11,6 +11,11 @@ return pht('Sleep for a specified number of seconds.'); } + + public function getBuildStepGroupKey() { + return HarbormasterTestBuildStepGroup::GROUPKEY; + } + public function getDescription() { return pht( 'Sleep for %s seconds.', diff --git a/src/applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php b/src/applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php --- a/src/applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php +++ b/src/applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php @@ -11,6 +11,10 @@ return pht('Throw an exception.'); } + public function getBuildStepGroupKey() { + return HarbormasterTestBuildStepGroup::GROUPKEY; + } + public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { diff --git a/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php @@ -11,6 +11,10 @@ return pht('Upload a file from a host to Phabricator.'); } + public function getBuildStepGroupKey() { + return HarbormasterPrototypeBuildStepGroup::GROUPKEY; + } + public function getDescription() { return pht( 'Upload %s from %s.', diff --git a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php --- a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php @@ -13,6 +13,10 @@ 'before continuing.'); } + public function getBuildStepGroupKey() { + return HarbormasterPrototypeBuildStepGroup::GROUPKEY; + } + public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { diff --git a/src/applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php b/src/applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php new file mode 100644 --- /dev/null +++ b/src/applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php @@ -0,0 +1,52 @@ +getConstant('GROUPKEY'); + if ($const === false) { + throw new Exception( + pht( + '"%s" class "%s" must define a "%s" property.', + __CLASS__, + get_class($this), + 'GROUPKEY')); + } + + return $const; + } + + final public static function getAllGroups() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getGroupKey') + ->setSortMethod('getGroupOrder') + ->execute(); + } + + final public static function getAllEnabledGroups() { + $groups = self::getAllGroups(); + + foreach ($groups as $key => $group) { + if (!$group->isEnabled()) { + unset($groups[$key]); + } + } + + return $groups; + } + +} diff --git a/src/applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php b/src/applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php new file mode 100644 --- /dev/null +++ b/src/applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php @@ -0,0 +1,20 @@ +