@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow the "Create Subtask" workflow to prompt for a subtype selection, and prepare for customizable options

Summary:
Ref T13222. Ref T12588. See PHI683. Currently, "Create Subtask" always uses the first edit form that the user has access to for the same task subtype. (For example, if you "Create Subtask" from a "Bug", you get the first edit form for "Bugs".)

I didn't want to go too crazy with the initial subtype implementation, but it seems like we're generally on firm ground and it's working fairly well: user requests are for more flexibility in using the system as implemented, not changes to the system or confusion/difficulty with any of the tradeoffs. Thus, I'm generally comfortable continuing to build it out in the same direction. To improve flexibility, I want to make the options from "Create Subtask" more flexible/configurable.

I plan to let you specify that a given subtype (say, "Quest") prompts you with creation options for a set of other subtypes (say, "Objective"), or prompts you with a particular set of forms.

If we end up with a single option, we just go into the current flow (directly to the edit form). If we end up with more than one option, we prompt the user to choose between them.

This change is a first step toward this:

- When building "Create Subtask", query for multiple forms.
- The default behavior is now "prompt user to choose among create forms of the same subtype". Previously, it was "use the first edit form of the same subtype". This is a behavioral change.
- The next change will make the selected forms configurable.
- (I also plan to make the dialog itself less rough.)

Test Plan: {F6051067}

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13222, T12588

Differential Revision: https://secure.phabricator.com/D19853

+147 -15
+2
src/__phutil_library_map__.php
··· 1760 1760 'ManiphestTaskStatusTestCase' => 'applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php', 1761 1761 'ManiphestTaskStatusTransaction' => 'applications/maniphest/xaction/ManiphestTaskStatusTransaction.php', 1762 1762 'ManiphestTaskSubpriorityTransaction' => 'applications/maniphest/xaction/ManiphestTaskSubpriorityTransaction.php', 1763 + 'ManiphestTaskSubtaskController' => 'applications/maniphest/controller/ManiphestTaskSubtaskController.php', 1763 1764 'ManiphestTaskSubtypeDatasource' => 'applications/maniphest/typeahead/ManiphestTaskSubtypeDatasource.php', 1764 1765 'ManiphestTaskTestCase' => 'applications/maniphest/__tests__/ManiphestTaskTestCase.php', 1765 1766 'ManiphestTaskTitleHeraldField' => 'applications/maniphest/herald/ManiphestTaskTitleHeraldField.php', ··· 7347 7348 'ManiphestTaskStatusTestCase' => 'PhabricatorTestCase', 7348 7349 'ManiphestTaskStatusTransaction' => 'ManiphestTaskTransactionType', 7349 7350 'ManiphestTaskSubpriorityTransaction' => 'ManiphestTaskTransactionType', 7351 + 'ManiphestTaskSubtaskController' => 'ManiphestController', 7350 7352 'ManiphestTaskSubtypeDatasource' => 'PhabricatorTypeaheadDatasource', 7351 7353 'ManiphestTaskTestCase' => 'PhabricatorTestCase', 7352 7354 'ManiphestTaskTitleHeraldField' => 'ManiphestTaskHeraldField',
+1
src/applications/maniphest/application/PhabricatorManiphestApplication.php
··· 52 52 'task/' => array( 53 53 $this->getEditRoutePattern('edit/') 54 54 => 'ManiphestTaskEditController', 55 + 'subtask/(?P<id>[1-9]\d*)/' => 'ManiphestTaskSubtaskController', 55 56 ), 56 57 'subpriority/' => 'ManiphestSubpriorityController', 57 58 ),
+24 -14
src/applications/maniphest/controller/ManiphestTaskDetailController.php
··· 281 281 ->setDisabled(!$can_edit) 282 282 ->setWorkflow($workflow_edit)); 283 283 284 - $edit_config = $edit_engine->loadDefaultEditConfiguration($task); 285 - $can_create = (bool)$edit_config; 284 + $subtype_map = $task->newEditEngineSubtypeMap(); 285 + $subtask_options = $subtype_map->getCreateFormsForSubtype( 286 + $edit_engine, 287 + $task); 288 + 289 + // If no forms are available, we want to show the user an error. 290 + // If one form is available, we take them user directly to the form. 291 + // If two or more forms are available, we give the user a choice. 292 + 293 + // The "subtask" controller handles the first case (no forms) and the 294 + // third case (more than one form). In the case of one form, we link 295 + // directly to the form. 296 + $subtask_uri = "/task/subtask/{$id}/"; 297 + $subtask_workflow = true; 286 298 287 - if ($can_create) { 288 - $form_key = $edit_config->getIdentifier(); 289 - $edit_uri = id(new PhutilURI("/task/edit/form/{$form_key}/")) 299 + if (count($subtask_options) == 1) { 300 + $subtask_form = head($subtask_options); 301 + $form_key = $subtask_form->getIdentifier(); 302 + $subtask_uri = id(new PhutilURI("/task/edit/form/{$form_key}/")) 290 303 ->setQueryParam('parent', $id) 291 304 ->setQueryParam('template', $id) 292 305 ->setQueryParam('status', ManiphestTaskStatus::getDefaultStatus()); 293 - $edit_uri = $this->getApplicationURI($edit_uri); 294 - } else { 295 - // TODO: This will usually give us a somewhat-reasonable error page, but 296 - // could be a bit cleaner. 297 - $edit_uri = "/task/edit/{$id}/"; 298 - $edit_uri = $this->getApplicationURI($edit_uri); 306 + $subtask_workflow = false; 299 307 } 308 + 309 + $subtask_uri = $this->getApplicationURI($subtask_uri); 300 310 301 311 $subtask_item = id(new PhabricatorActionView()) 302 312 ->setName(pht('Create Subtask')) 303 - ->setHref($edit_uri) 313 + ->setHref($subtask_uri) 304 314 ->setIcon('fa-level-down') 305 - ->setDisabled(!$can_create) 306 - ->setWorkflow(!$can_create); 315 + ->setDisabled(!$subtask_options) 316 + ->setWorkflow($subtask_workflow); 307 317 308 318 $relationship_list = PhabricatorObjectRelationshipList::newForObject( 309 319 $viewer,
+76
src/applications/maniphest/controller/ManiphestTaskSubtaskController.php
··· 1 + <?php 2 + 3 + final class ManiphestTaskSubtaskController 4 + extends ManiphestController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $this->getViewer(); 8 + $id = $request->getURIData('id'); 9 + 10 + $task = id(new ManiphestTaskQuery()) 11 + ->setViewer($viewer) 12 + ->withIDs(array($id)) 13 + ->executeOne(); 14 + if (!$task) { 15 + return new Aphront404Response(); 16 + } 17 + 18 + $cancel_uri = $task->getURI(); 19 + 20 + $edit_engine = id(new ManiphestEditEngine()) 21 + ->setViewer($viewer) 22 + ->setTargetObject($task); 23 + 24 + $subtype_map = $task->newEditEngineSubtypeMap(); 25 + 26 + $subtype_options = $subtype_map->getCreateFormsForSubtype( 27 + $edit_engine, 28 + $task); 29 + 30 + if (!$subtype_options) { 31 + return $this->newDialog() 32 + ->setTitle(pht('No Forms')) 33 + ->appendParagraph( 34 + pht( 35 + 'You do not have access to any forms which can be used to '. 36 + 'create a subtask.')) 37 + ->addCancelButton($cancel_uri, pht('Close')); 38 + } 39 + 40 + if ($request->isFormPost()) { 41 + $form_key = $request->getStr('formKey'); 42 + if (isset($subtype_options[$form_key])) { 43 + $subtask_uri = id(new PhutilURI("/task/edit/form/{$form_key}/")) 44 + ->setQueryParam('parent', $id) 45 + ->setQueryParam('template', $id) 46 + ->setQueryParam('status', ManiphestTaskStatus::getDefaultStatus()); 47 + $subtask_uri = $this->getApplicationURI($subtask_uri); 48 + 49 + return id(new AphrontRedirectResponse()) 50 + ->setURI($subtask_uri); 51 + } 52 + } 53 + 54 + $control = id(new AphrontFormRadioButtonControl()) 55 + ->setName('formKey') 56 + ->setLabel(pht('Subtype')); 57 + 58 + foreach ($subtype_options as $key => $subtype_form) { 59 + $control->addButton( 60 + $key, 61 + $subtype_form->getDisplayName(), 62 + null); 63 + } 64 + 65 + $form = id(new AphrontFormView()) 66 + ->setViewer($viewer) 67 + ->appendControl($control); 68 + 69 + return $this->newDialog() 70 + ->setTitle(pht('Choose Subtype')) 71 + ->appendForm($form) 72 + ->addSubmitButton(pht('Continue')) 73 + ->addCancelButton($cancel_uri); 74 + } 75 + 76 + }
+1 -1
src/applications/transactions/editengine/PhabricatorEditEngine.php
··· 358 358 return $this->editEngineConfiguration; 359 359 } 360 360 361 - private function newConfigurationQuery() { 361 + public function newConfigurationQuery() { 362 362 return id(new PhabricatorEditEngineConfigurationQuery()) 363 363 ->setViewer($this->getViewer()) 364 364 ->withEngineKeys(array($this->getEngineKey()));
+43
src/applications/transactions/editengine/PhabricatorEditEngineSubtypeMap.php
··· 39 39 return $this->subtypes[$subtype_key]; 40 40 } 41 41 42 + public function getCreateFormsForSubtype( 43 + PhabricatorEditEngine $edit_engine, 44 + PhabricatorEditEngineSubtypeInterface $object) { 45 + 46 + $subtype_key = $object->getEditEngineSubtype(); 47 + $subtype = $this->getSubtype($subtype_key); 48 + 49 + // TODO: Allow subtype configuration to specify that children should be 50 + // created from particular forms or subtypes. 51 + $select_ids = array(); 52 + $select_subtypes = array(); 53 + 54 + $query = $edit_engine->newConfigurationQuery() 55 + ->withIsDisabled(false); 56 + 57 + if ($select_ids) { 58 + $query->withIDs($select_ids); 59 + } else { 60 + // If we're selecting by subtype rather than selecting specific forms, 61 + // only select create forms. 62 + $query->withIsDefault(true); 63 + 64 + if ($select_subtypes) { 65 + $query->withSubtypes($select_subtypes); 66 + } else { 67 + $query->withSubtypes(array($subtype_key)); 68 + } 69 + } 70 + 71 + $forms = $query->execute(); 72 + $forms = mpull($forms, null, 'getIdentifier'); 73 + 74 + // If we're selecting by ID, respect the order specified in the 75 + // constraint. Otherwise, use the create form sort order. 76 + if ($select_ids) { 77 + $forms = array_select_keys($forms, $select_ids) + $forms; 78 + } else { 79 + $forms = msort($forms, 'getCreateSortKey'); 80 + } 81 + 82 + return $forms; 83 + } 84 + 42 85 }