diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index cd260f497d..184c2a3d7c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1760,6 +1760,7 @@ phutil_register_library_map(array( 'ManiphestTaskStatusTestCase' => 'applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php', 'ManiphestTaskStatusTransaction' => 'applications/maniphest/xaction/ManiphestTaskStatusTransaction.php', 'ManiphestTaskSubpriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php', + 'ManiphestTaskSubtaskController' => 'applications/maniphest/controller/ManiphestTaskSubtaskController.php', 'ManiphestTaskSubtypeDatasource' => 'applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php', 'ManiphestTaskTestCase' => 'applications/maniphest/__tests__/ManiphestTaskTestCase.php', 'ManiphestTaskTitleHeraldField' => 'applications/maniphest/herald/ManiphestTaskTitleHeraldField.php', @@ -7347,6 +7348,7 @@ phutil_register_library_map(array( 'ManiphestTaskStatusTestCase' => 'PhabricatorTestCase', 'ManiphestTaskStatusTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskSubpriorityTransaction' => 'ManiphestTaskTransactionType', + 'ManiphestTaskSubtaskController' => 'ManiphestController', 'ManiphestTaskSubtypeDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskTestCase' => 'PhabricatorTestCase', 'ManiphestTaskTitleHeraldField' => 'ManiphestTaskHeraldField', diff --git a/src/applications/maniphest/application/PhabricatorManiphestApplication.php b/src/applications/maniphest/application/PhabricatorManiphestApplication.php index 1ed4096660..35c1efb6e8 100644 --- a/src/applications/maniphest/application/PhabricatorManiphestApplication.php +++ b/src/applications/maniphest/application/PhabricatorManiphestApplication.php @@ -52,6 +52,7 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication { 'task/' => array( $this->getEditRoutePattern('edit/') => 'ManiphestTaskEditController', + 'subtask/(?P[1-9]\d*)/' => 'ManiphestTaskSubtaskController', ), 'subpriority/' => 'ManiphestSubpriorityController', ), diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index a11cc4d85f..0f96d76b91 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -281,29 +281,39 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setDisabled(!$can_edit) ->setWorkflow($workflow_edit)); - $edit_config = $edit_engine->loadDefaultEditConfiguration($task); - $can_create = (bool)$edit_config; + $subtype_map = $task->newEditEngineSubtypeMap(); + $subtask_options = $subtype_map->getCreateFormsForSubtype( + $edit_engine, + $task); - if ($can_create) { - $form_key = $edit_config->getIdentifier(); - $edit_uri = id(new PhutilURI("/task/edit/form/{$form_key}/")) + // If no forms are available, we want to show the user an error. + // If one form is available, we take them user directly to the form. + // If two or more forms are available, we give the user a choice. + + // The "subtask" controller handles the first case (no forms) and the + // third case (more than one form). In the case of one form, we link + // directly to the form. + $subtask_uri = "/task/subtask/{$id}/"; + $subtask_workflow = true; + + if (count($subtask_options) == 1) { + $subtask_form = head($subtask_options); + $form_key = $subtask_form->getIdentifier(); + $subtask_uri = id(new PhutilURI("/task/edit/form/{$form_key}/")) ->setQueryParam('parent', $id) ->setQueryParam('template', $id) ->setQueryParam('status', ManiphestTaskStatus::getDefaultStatus()); - $edit_uri = $this->getApplicationURI($edit_uri); - } else { - // TODO: This will usually give us a somewhat-reasonable error page, but - // could be a bit cleaner. - $edit_uri = "/task/edit/{$id}/"; - $edit_uri = $this->getApplicationURI($edit_uri); + $subtask_workflow = false; } + $subtask_uri = $this->getApplicationURI($subtask_uri); + $subtask_item = id(new PhabricatorActionView()) ->setName(pht('Create Subtask')) - ->setHref($edit_uri) + ->setHref($subtask_uri) ->setIcon('fa-level-down') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create); + ->setDisabled(!$subtask_options) + ->setWorkflow($subtask_workflow); $relationship_list = PhabricatorObjectRelationshipList::newForObject( $viewer, diff --git a/src/applications/maniphest/controller/ManiphestTaskSubtaskController.php b/src/applications/maniphest/controller/ManiphestTaskSubtaskController.php new file mode 100644 index 0000000000..9da0ed4206 --- /dev/null +++ b/src/applications/maniphest/controller/ManiphestTaskSubtaskController.php @@ -0,0 +1,76 @@ +getViewer(); + $id = $request->getURIData('id'); + + $task = id(new ManiphestTaskQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$task) { + return new Aphront404Response(); + } + + $cancel_uri = $task->getURI(); + + $edit_engine = id(new ManiphestEditEngine()) + ->setViewer($viewer) + ->setTargetObject($task); + + $subtype_map = $task->newEditEngineSubtypeMap(); + + $subtype_options = $subtype_map->getCreateFormsForSubtype( + $edit_engine, + $task); + + if (!$subtype_options) { + return $this->newDialog() + ->setTitle(pht('No Forms')) + ->appendParagraph( + pht( + 'You do not have access to any forms which can be used to '. + 'create a subtask.')) + ->addCancelButton($cancel_uri, pht('Close')); + } + + if ($request->isFormPost()) { + $form_key = $request->getStr('formKey'); + if (isset($subtype_options[$form_key])) { + $subtask_uri = id(new PhutilURI("/task/edit/form/{$form_key}/")) + ->setQueryParam('parent', $id) + ->setQueryParam('template', $id) + ->setQueryParam('status', ManiphestTaskStatus::getDefaultStatus()); + $subtask_uri = $this->getApplicationURI($subtask_uri); + + return id(new AphrontRedirectResponse()) + ->setURI($subtask_uri); + } + } + + $control = id(new AphrontFormRadioButtonControl()) + ->setName('formKey') + ->setLabel(pht('Subtype')); + + foreach ($subtype_options as $key => $subtype_form) { + $control->addButton( + $key, + $subtype_form->getDisplayName(), + null); + } + + $form = id(new AphrontFormView()) + ->setViewer($viewer) + ->appendControl($control); + + return $this->newDialog() + ->setTitle(pht('Choose Subtype')) + ->appendForm($form) + ->addSubmitButton(pht('Continue')) + ->addCancelButton($cancel_uri); + } + +} diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 4e189df164..86cc014102 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -358,7 +358,7 @@ abstract class PhabricatorEditEngine return $this->editEngineConfiguration; } - private function newConfigurationQuery() { + public function newConfigurationQuery() { return id(new PhabricatorEditEngineConfigurationQuery()) ->setViewer($this->getViewer()) ->withEngineKeys(array($this->getEngineKey())); diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineSubtypeMap.php b/src/applications/transactions/editengine/PhabricatorEditEngineSubtypeMap.php index cd8e0674ae..c76911dbc3 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngineSubtypeMap.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngineSubtypeMap.php @@ -39,4 +39,47 @@ final class PhabricatorEditEngineSubtypeMap return $this->subtypes[$subtype_key]; } + public function getCreateFormsForSubtype( + PhabricatorEditEngine $edit_engine, + PhabricatorEditEngineSubtypeInterface $object) { + + $subtype_key = $object->getEditEngineSubtype(); + $subtype = $this->getSubtype($subtype_key); + + // TODO: Allow subtype configuration to specify that children should be + // created from particular forms or subtypes. + $select_ids = array(); + $select_subtypes = array(); + + $query = $edit_engine->newConfigurationQuery() + ->withIsDisabled(false); + + if ($select_ids) { + $query->withIDs($select_ids); + } else { + // If we're selecting by subtype rather than selecting specific forms, + // only select create forms. + $query->withIsDefault(true); + + if ($select_subtypes) { + $query->withSubtypes($select_subtypes); + } else { + $query->withSubtypes(array($subtype_key)); + } + } + + $forms = $query->execute(); + $forms = mpull($forms, null, 'getIdentifier'); + + // If we're selecting by ID, respect the order specified in the + // constraint. Otherwise, use the create form sort order. + if ($select_ids) { + $forms = array_select_keys($forms, $select_ids) + $forms; + } else { + $forms = msort($forms, 'getCreateSortKey'); + } + + return $forms; + } + }