@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.

Add a "Batch Edit Tasks..." action to workboard columns

Summary:
Ref T5523. Adds a new workflow to make some kinds of bulk workboard operations easier.

New dropdown action:

{F376848}

This brings you into the existing bulk edit flow:

{F376849}

When you save an edit, you're taken back to the board:

{F376850}

If you try to edit a column with nothing in it, you get an error:

{F376851}

Note that the selected workboard filter is applied before choosing tasks, so if your filter is set to "open tasks" we only batch edit the open (i.e., currently visible) tasks in the column. I think this is more powerful (it lets you use filtering to select task subsets) but might not be completely obvious in all cases (although I do think it's more obvious than the alternative rule -- just an issue of neither rule being completely obvious).

Test Plan:
- Batch edited tasks in a column.
- Used "Batch Edit Tasks..." to move tasks to a different workboard by removing + adding a project.
- Batch edited a column with filtered-out tasks, verified only visible tasks were edited.
- Batch edited a column with no visible tasks, received error.
- Used the batch editor normally (Maniphest -> Maniphest, no boards).

Reviewers: chad, btrahan

Reviewed By: btrahan

Subscribers: johnny-bit, cburroughs, epriestley

Projects: #prioritized

Maniphest Tasks: T5523

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

+125 -32
+15 -15
resources/celerity/map.php
··· 8 8 return array( 9 9 'names' => array( 10 10 'core.pkg.css' => 'bf29d341', 11 - 'core.pkg.js' => 'dfea788f', 11 + 'core.pkg.js' => 'a626d14c', 12 12 'darkconsole.pkg.js' => '8ab24e01', 13 13 'differential.pkg.css' => '3500921f', 14 14 'differential.pkg.js' => 'c0506961', ··· 450 450 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', 451 451 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 452 452 'rsrc/js/core/Notification.js' => '0c6946e7', 453 - 'rsrc/js/core/Prefab.js' => '6920d200', 453 + 'rsrc/js/core/Prefab.js' => 'b972bdcd', 454 454 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 455 455 'rsrc/js/core/TextAreaUtils.js' => '5c93c52c', 456 456 'rsrc/js/core/Title.js' => 'df5e11d2', ··· 744 744 'phabricator-notification-menu-css' => '3c9d8aa1', 745 745 'phabricator-object-selector-css' => '029a133d', 746 746 'phabricator-phtize' => 'd254d646', 747 - 'phabricator-prefab' => '6920d200', 747 + 'phabricator-prefab' => 'b972bdcd', 748 748 'phabricator-profile-css' => '1a20dcbf', 749 749 'phabricator-remarkup-css' => 'e10512ff', 750 750 'phabricator-search-results-css' => '15c71110', ··· 1296 1296 '6882e80a' => array( 1297 1297 'javelin-dom', 1298 1298 ), 1299 - '6920d200' => array( 1300 - 'javelin-install', 1301 - 'javelin-util', 1302 - 'javelin-dom', 1303 - 'javelin-typeahead', 1304 - 'javelin-tokenizer', 1305 - 'javelin-typeahead-preloaded-source', 1306 - 'javelin-typeahead-ondemand-source', 1307 - 'javelin-dom', 1308 - 'javelin-stratcom', 1309 - 'javelin-util', 1310 - ), 1311 1299 '69adf288' => array( 1312 1300 'javelin-install', 1313 1301 ), ··· 1717 1705 'javelin-install', 1718 1706 'javelin-stratcom', 1719 1707 'javelin-dom', 1708 + 'javelin-util', 1709 + ), 1710 + 'b972bdcd' => array( 1711 + 'javelin-install', 1712 + 'javelin-util', 1713 + 'javelin-dom', 1714 + 'javelin-typeahead', 1715 + 'javelin-tokenizer', 1716 + 'javelin-typeahead-preloaded-source', 1717 + 'javelin-typeahead-ondemand-source', 1718 + 'javelin-dom', 1719 + 'javelin-stratcom', 1720 1720 'javelin-util', 1721 1721 ), 1722 1722 'bba9eedf' => array(
+38 -17
src/applications/maniphest/controller/ManiphestBatchEditController.php
··· 2 2 3 3 final class ManiphestBatchEditController extends ManiphestController { 4 4 5 - public function processRequest() { 5 + public function handleRequest(AphrontRequest $request) { 6 + $viewer = $this->getViewer(); 7 + 6 8 $this->requireApplicationCapability( 7 9 ManiphestBulkEditCapability::CAPABILITY); 8 10 9 - $request = $this->getRequest(); 10 - $user = $request->getUser(); 11 + $project = null; 12 + $board_id = $request->getInt('board'); 13 + if ($board_id) { 14 + $project = id(new PhabricatorProjectQuery()) 15 + ->setViewer($viewer) 16 + ->withIDs(array($board_id)) 17 + ->executeOne(); 18 + if (!$project) { 19 + return new Aphront404Response(); 20 + } 21 + } 11 22 12 23 $task_ids = $request->getArr('batch'); 24 + if (!$task_ids) { 25 + $task_ids = $request->getStrList('batch'); 26 + } 27 + 13 28 $tasks = id(new ManiphestTaskQuery()) 14 - ->setViewer($user) 29 + ->setViewer($viewer) 15 30 ->withIDs($task_ids) 16 31 ->requireCapabilities( 17 32 array( ··· 22 37 ->needProjectPHIDs(true) 23 38 ->execute(); 24 39 40 + if ($project) { 41 + $cancel_uri = '/project/board/'.$project->getID().'/'; 42 + $redirect_uri = $cancel_uri; 43 + } else { 44 + $cancel_uri = '/maniphest/'; 45 + $redirect_uri = '/maniphest/?ids='.implode(',', mpull($tasks, 'getID')); 46 + } 47 + 25 48 $actions = $request->getStr('actions'); 26 49 if ($actions) { 27 50 $actions = json_decode($actions, true); ··· 39 62 // TODO: Set content source to "batch edit". 40 63 41 64 $editor = id(new ManiphestTransactionEditor()) 42 - ->setActor($user) 65 + ->setActor($viewer) 43 66 ->setContentSourceFromRequest($request) 44 67 ->setContinueOnNoEffect(true) 45 68 ->setContinueOnMissingFields(true) ··· 47 70 } 48 71 } 49 72 50 - $task_ids = implode(',', mpull($tasks, 'getID')); 51 - 52 - return id(new AphrontRedirectResponse()) 53 - ->setURI('/maniphest/?ids='.$task_ids); 73 + return id(new AphrontRedirectResponse())->setURI($redirect_uri); 54 74 } 55 75 56 - $handles = ManiphestTaskListView::loadTaskHandles($user, $tasks); 76 + $handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); 57 77 58 78 $list = new ManiphestTaskListView(); 59 79 $list->setTasks($tasks); 60 - $list->setUser($user); 80 + $list->setUser($viewer); 61 81 $list->setHandles($handles); 62 82 63 83 $template = new AphrontTokenizerTemplateView(); ··· 65 85 66 86 $projects_source = new PhabricatorProjectDatasource(); 67 87 $mailable_source = new PhabricatorMetaMTAMailableDatasource(); 68 - $mailable_source->setViewer($user); 88 + $mailable_source->setViewer($viewer); 69 89 $owner_source = new PhabricatorTypeaheadOwnerDatasource(); 70 - $owner_source->setViewer($user); 90 + $owner_source->setViewer($viewer); 71 91 72 92 require_celerity_resource('maniphest-batch-editor'); 73 93 Javelin::initBehavior( ··· 98 118 'statusMap' => ManiphestTaskStatus::getTaskStatusMap(), 99 119 )); 100 120 101 - $form = new AphrontFormView(); 102 - $form->setUser($user); 103 - $form->setID('maniphest-batch-edit-form'); 121 + $form = id(new AphrontFormView()) 122 + ->setUser($viewer) 123 + ->addHiddenInput('board', $board_id) 124 + ->setID('maniphest-batch-edit-form'); 104 125 105 126 foreach ($tasks as $task) { 106 127 $form->appendChild( ··· 143 164 ->appendChild( 144 165 id(new AphrontFormSubmitControl()) 145 166 ->setValue(pht('Update Tasks')) 146 - ->addCancelButton('/maniphest/')); 167 + ->addCancelButton($cancel_uri)); 147 168 148 169 $title = pht('Batch Editor'); 149 170
+72
src/applications/project/controller/PhabricatorProjectBoardViewController.php
··· 3 3 final class PhabricatorProjectBoardViewController 4 4 extends PhabricatorProjectBoardController { 5 5 6 + const BATCH_EDIT_ALL = 'all'; 7 + 6 8 private $id; 7 9 private $slug; 8 10 private $handles; ··· 211 213 ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)) 212 214 ->apply($tasks); 213 215 216 + // If this is a batch edit, select the editable tasks in the chosen column 217 + // and ship the user into the batch editor. 218 + $batch_edit = $request->getStr('batch'); 219 + if ($batch_edit) { 220 + if ($batch_edit !== self::BATCH_EDIT_ALL) { 221 + $column_id_map = mpull($columns, null, 'getID'); 222 + $batch_column = idx($column_id_map, $batch_edit); 223 + if (!$batch_column) { 224 + return new Aphront404Response(); 225 + } 226 + 227 + $batch_task_phids = idx($task_map, $batch_column->getPHID(), array()); 228 + foreach ($batch_task_phids as $key => $batch_task_phid) { 229 + if (empty($task_can_edit_map[$batch_task_phid])) { 230 + unset($batch_task_phids[$key]); 231 + } 232 + } 233 + 234 + $batch_tasks = array_select_keys($tasks, $batch_task_phids); 235 + } else { 236 + $batch_tasks = $task_can_edit_map; 237 + } 238 + 239 + if (!$batch_tasks) { 240 + $cancel_uri = $this->getURIWithState($board_uri); 241 + return $this->newDialog() 242 + ->setTitle(pht('No Editable Tasks')) 243 + ->appendParagraph( 244 + pht( 245 + 'The selected column contains no visible tasks which you '. 246 + 'have permission to edit.')) 247 + ->addCancelButton($board_uri); 248 + } 249 + 250 + $batch_ids = mpull($batch_tasks, 'getID'); 251 + $batch_ids = implode(',', $batch_ids); 252 + 253 + $batch_uri = new PhutilURI('/maniphest/batch/'); 254 + $batch_uri->setQueryParam('board', $this->id); 255 + $batch_uri->setQueryParam('batch', $batch_ids); 256 + return id(new AphrontRedirectResponse()) 257 + ->setURI($batch_uri); 258 + } 259 + 214 260 $board_id = celerity_generate_unique_node_id(); 215 261 216 262 $board = id(new PHUIWorkboardView()) ··· 518 564 ->setName($hidden_text) 519 565 ->setHref($hidden_uri); 520 566 567 + $batch_edit_uri = $request->getRequestURI(); 568 + $batch_edit_uri->setQueryParam('batch', self::BATCH_EDIT_ALL); 569 + $can_batch_edit = PhabricatorPolicyFilter::hasCapability( 570 + $viewer, 571 + PhabricatorApplication::getByClass('PhabricatorManiphestApplication'), 572 + ManiphestBulkEditCapability::CAPABILITY); 573 + 574 + $manage_items[] = id(new PhabricatorActionView()) 575 + ->setIcon('fa-list-ul') 576 + ->setName(pht('Batch Edit Visible Tasks...')) 577 + ->setHref($batch_edit_uri) 578 + ->setDisabled(!$can_batch_edit); 579 + 521 580 $manage_menu = id(new PhabricatorActionListView()) 522 581 ->setUser($viewer); 523 582 foreach ($manage_items as $item) { ··· 562 621 'columnPHID' => $column->getPHID(), 563 622 )) 564 623 ->setDisabled(!$can_edit); 624 + 625 + $batch_edit_uri = $request->getRequestURI(); 626 + $batch_edit_uri->setQueryParam('batch', $column->getID()); 627 + $can_batch_edit = PhabricatorPolicyFilter::hasCapability( 628 + $viewer, 629 + PhabricatorApplication::getByClass('PhabricatorManiphestApplication'), 630 + ManiphestBulkEditCapability::CAPABILITY); 631 + 632 + $column_items[] = id(new PhabricatorActionView()) 633 + ->setIcon('fa-list-ul') 634 + ->setName(pht('Batch Edit Tasks...')) 635 + ->setHref($batch_edit_uri) 636 + ->setDisabled(!$can_batch_edit); 565 637 566 638 $edit_uri = $this->getApplicationURI( 567 639 'board/'.$this->id.'/column/'.$column->getID().'/');