From e0c95bca863115da92f464ba4e40113b346d0ca2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 20 Apr 2015 10:05:08 -0700 Subject: [PATCH] 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 --- resources/celerity/map.php | 30 ++++---- .../ManiphestBatchEditController.php | 55 +++++++++----- .../PhabricatorProjectBoardViewController.php | 72 +++++++++++++++++++ 3 files changed, 125 insertions(+), 32 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 619fdf4efe..996a6d44d3 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => 'bf29d341', - 'core.pkg.js' => 'dfea788f', + 'core.pkg.js' => 'a626d14c', 'darkconsole.pkg.js' => '8ab24e01', 'differential.pkg.css' => '3500921f', 'differential.pkg.js' => 'c0506961', @@ -450,7 +450,7 @@ return array( 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 'rsrc/js/core/Notification.js' => '0c6946e7', - 'rsrc/js/core/Prefab.js' => '6920d200', + 'rsrc/js/core/Prefab.js' => 'b972bdcd', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '5c93c52c', 'rsrc/js/core/Title.js' => 'df5e11d2', @@ -744,7 +744,7 @@ return array( 'phabricator-notification-menu-css' => '3c9d8aa1', 'phabricator-object-selector-css' => '029a133d', 'phabricator-phtize' => 'd254d646', - 'phabricator-prefab' => '6920d200', + 'phabricator-prefab' => 'b972bdcd', 'phabricator-profile-css' => '1a20dcbf', 'phabricator-remarkup-css' => 'e10512ff', 'phabricator-search-results-css' => '15c71110', @@ -1296,18 +1296,6 @@ return array( '6882e80a' => array( 'javelin-dom', ), - '6920d200' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-typeahead', - 'javelin-tokenizer', - 'javelin-typeahead-preloaded-source', - 'javelin-typeahead-ondemand-source', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - ), '69adf288' => array( 'javelin-install', ), @@ -1719,6 +1707,18 @@ return array( 'javelin-dom', 'javelin-util', ), + 'b972bdcd' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-typeahead', + 'javelin-tokenizer', + 'javelin-typeahead-preloaded-source', + 'javelin-typeahead-ondemand-source', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + ), 'bba9eedf' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/maniphest/controller/ManiphestBatchEditController.php b/src/applications/maniphest/controller/ManiphestBatchEditController.php index f604d58d0c..98bf067096 100644 --- a/src/applications/maniphest/controller/ManiphestBatchEditController.php +++ b/src/applications/maniphest/controller/ManiphestBatchEditController.php @@ -2,16 +2,31 @@ final class ManiphestBatchEditController extends ManiphestController { - public function processRequest() { + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $this->requireApplicationCapability( ManiphestBulkEditCapability::CAPABILITY); - $request = $this->getRequest(); - $user = $request->getUser(); + $project = null; + $board_id = $request->getInt('board'); + if ($board_id) { + $project = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withIDs(array($board_id)) + ->executeOne(); + if (!$project) { + return new Aphront404Response(); + } + } $task_ids = $request->getArr('batch'); + if (!$task_ids) { + $task_ids = $request->getStrList('batch'); + } + $tasks = id(new ManiphestTaskQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs($task_ids) ->requireCapabilities( array( @@ -22,6 +37,14 @@ final class ManiphestBatchEditController extends ManiphestController { ->needProjectPHIDs(true) ->execute(); + if ($project) { + $cancel_uri = '/project/board/'.$project->getID().'/'; + $redirect_uri = $cancel_uri; + } else { + $cancel_uri = '/maniphest/'; + $redirect_uri = '/maniphest/?ids='.implode(',', mpull($tasks, 'getID')); + } + $actions = $request->getStr('actions'); if ($actions) { $actions = json_decode($actions, true); @@ -39,7 +62,7 @@ final class ManiphestBatchEditController extends ManiphestController { // TODO: Set content source to "batch edit". $editor = id(new ManiphestTransactionEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) @@ -47,17 +70,14 @@ final class ManiphestBatchEditController extends ManiphestController { } } - $task_ids = implode(',', mpull($tasks, 'getID')); - - return id(new AphrontRedirectResponse()) - ->setURI('/maniphest/?ids='.$task_ids); + return id(new AphrontRedirectResponse())->setURI($redirect_uri); } - $handles = ManiphestTaskListView::loadTaskHandles($user, $tasks); + $handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); $list = new ManiphestTaskListView(); $list->setTasks($tasks); - $list->setUser($user); + $list->setUser($viewer); $list->setHandles($handles); $template = new AphrontTokenizerTemplateView(); @@ -65,9 +85,9 @@ final class ManiphestBatchEditController extends ManiphestController { $projects_source = new PhabricatorProjectDatasource(); $mailable_source = new PhabricatorMetaMTAMailableDatasource(); - $mailable_source->setViewer($user); + $mailable_source->setViewer($viewer); $owner_source = new PhabricatorTypeaheadOwnerDatasource(); - $owner_source->setViewer($user); + $owner_source->setViewer($viewer); require_celerity_resource('maniphest-batch-editor'); Javelin::initBehavior( @@ -98,9 +118,10 @@ final class ManiphestBatchEditController extends ManiphestController { 'statusMap' => ManiphestTaskStatus::getTaskStatusMap(), )); - $form = new AphrontFormView(); - $form->setUser($user); - $form->setID('maniphest-batch-edit-form'); + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->addHiddenInput('board', $board_id) + ->setID('maniphest-batch-edit-form'); foreach ($tasks as $task) { $form->appendChild( @@ -143,7 +164,7 @@ final class ManiphestBatchEditController extends ManiphestController { ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Update Tasks')) - ->addCancelButton('/maniphest/')); + ->addCancelButton($cancel_uri)); $title = pht('Batch Editor'); diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index c124ae71fc..925edeabea 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -3,6 +3,8 @@ final class PhabricatorProjectBoardViewController extends PhabricatorProjectBoardController { + const BATCH_EDIT_ALL = 'all'; + private $id; private $slug; private $handles; @@ -211,6 +213,50 @@ final class PhabricatorProjectBoardViewController ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)) ->apply($tasks); + // If this is a batch edit, select the editable tasks in the chosen column + // and ship the user into the batch editor. + $batch_edit = $request->getStr('batch'); + if ($batch_edit) { + if ($batch_edit !== self::BATCH_EDIT_ALL) { + $column_id_map = mpull($columns, null, 'getID'); + $batch_column = idx($column_id_map, $batch_edit); + if (!$batch_column) { + return new Aphront404Response(); + } + + $batch_task_phids = idx($task_map, $batch_column->getPHID(), array()); + foreach ($batch_task_phids as $key => $batch_task_phid) { + if (empty($task_can_edit_map[$batch_task_phid])) { + unset($batch_task_phids[$key]); + } + } + + $batch_tasks = array_select_keys($tasks, $batch_task_phids); + } else { + $batch_tasks = $task_can_edit_map; + } + + if (!$batch_tasks) { + $cancel_uri = $this->getURIWithState($board_uri); + return $this->newDialog() + ->setTitle(pht('No Editable Tasks')) + ->appendParagraph( + pht( + 'The selected column contains no visible tasks which you '. + 'have permission to edit.')) + ->addCancelButton($board_uri); + } + + $batch_ids = mpull($batch_tasks, 'getID'); + $batch_ids = implode(',', $batch_ids); + + $batch_uri = new PhutilURI('/maniphest/batch/'); + $batch_uri->setQueryParam('board', $this->id); + $batch_uri->setQueryParam('batch', $batch_ids); + return id(new AphrontRedirectResponse()) + ->setURI($batch_uri); + } + $board_id = celerity_generate_unique_node_id(); $board = id(new PHUIWorkboardView()) @@ -518,6 +564,19 @@ final class PhabricatorProjectBoardViewController ->setName($hidden_text) ->setHref($hidden_uri); + $batch_edit_uri = $request->getRequestURI(); + $batch_edit_uri->setQueryParam('batch', self::BATCH_EDIT_ALL); + $can_batch_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + PhabricatorApplication::getByClass('PhabricatorManiphestApplication'), + ManiphestBulkEditCapability::CAPABILITY); + + $manage_items[] = id(new PhabricatorActionView()) + ->setIcon('fa-list-ul') + ->setName(pht('Batch Edit Visible Tasks...')) + ->setHref($batch_edit_uri) + ->setDisabled(!$can_batch_edit); + $manage_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($manage_items as $item) { @@ -563,6 +622,19 @@ final class PhabricatorProjectBoardViewController )) ->setDisabled(!$can_edit); + $batch_edit_uri = $request->getRequestURI(); + $batch_edit_uri->setQueryParam('batch', $column->getID()); + $can_batch_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + PhabricatorApplication::getByClass('PhabricatorManiphestApplication'), + ManiphestBulkEditCapability::CAPABILITY); + + $column_items[] = id(new PhabricatorActionView()) + ->setIcon('fa-list-ul') + ->setName(pht('Batch Edit Tasks...')) + ->setHref($batch_edit_uri) + ->setDisabled(!$can_batch_edit); + $edit_uri = $this->getApplicationURI( 'board/'.$this->id.'/column/'.$column->getID().'/');