mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +01:00
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
This commit is contained in:
parent
2875f029be
commit
e0c95bca86
3 changed files with 125 additions and 32 deletions
|
@ -8,7 +8,7 @@
|
||||||
return array(
|
return array(
|
||||||
'names' => array(
|
'names' => array(
|
||||||
'core.pkg.css' => 'bf29d341',
|
'core.pkg.css' => 'bf29d341',
|
||||||
'core.pkg.js' => 'dfea788f',
|
'core.pkg.js' => 'a626d14c',
|
||||||
'darkconsole.pkg.js' => '8ab24e01',
|
'darkconsole.pkg.js' => '8ab24e01',
|
||||||
'differential.pkg.css' => '3500921f',
|
'differential.pkg.css' => '3500921f',
|
||||||
'differential.pkg.js' => 'c0506961',
|
'differential.pkg.js' => 'c0506961',
|
||||||
|
@ -450,7 +450,7 @@ return array(
|
||||||
'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
|
'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
|
||||||
'rsrc/js/core/MultirowRowManager.js' => 'b5d57730',
|
'rsrc/js/core/MultirowRowManager.js' => 'b5d57730',
|
||||||
'rsrc/js/core/Notification.js' => '0c6946e7',
|
'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/ShapedRequest.js' => '7cbe244b',
|
||||||
'rsrc/js/core/TextAreaUtils.js' => '5c93c52c',
|
'rsrc/js/core/TextAreaUtils.js' => '5c93c52c',
|
||||||
'rsrc/js/core/Title.js' => 'df5e11d2',
|
'rsrc/js/core/Title.js' => 'df5e11d2',
|
||||||
|
@ -744,7 +744,7 @@ return array(
|
||||||
'phabricator-notification-menu-css' => '3c9d8aa1',
|
'phabricator-notification-menu-css' => '3c9d8aa1',
|
||||||
'phabricator-object-selector-css' => '029a133d',
|
'phabricator-object-selector-css' => '029a133d',
|
||||||
'phabricator-phtize' => 'd254d646',
|
'phabricator-phtize' => 'd254d646',
|
||||||
'phabricator-prefab' => '6920d200',
|
'phabricator-prefab' => 'b972bdcd',
|
||||||
'phabricator-profile-css' => '1a20dcbf',
|
'phabricator-profile-css' => '1a20dcbf',
|
||||||
'phabricator-remarkup-css' => 'e10512ff',
|
'phabricator-remarkup-css' => 'e10512ff',
|
||||||
'phabricator-search-results-css' => '15c71110',
|
'phabricator-search-results-css' => '15c71110',
|
||||||
|
@ -1296,18 +1296,6 @@ return array(
|
||||||
'6882e80a' => array(
|
'6882e80a' => array(
|
||||||
'javelin-dom',
|
'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(
|
'69adf288' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
),
|
),
|
||||||
|
@ -1719,6 +1707,18 @@ return array(
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
'javelin-util',
|
'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(
|
'bba9eedf' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-stratcom',
|
'javelin-stratcom',
|
||||||
|
|
|
@ -2,16 +2,31 @@
|
||||||
|
|
||||||
final class ManiphestBatchEditController extends ManiphestController {
|
final class ManiphestBatchEditController extends ManiphestController {
|
||||||
|
|
||||||
public function processRequest() {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
$this->requireApplicationCapability(
|
$this->requireApplicationCapability(
|
||||||
ManiphestBulkEditCapability::CAPABILITY);
|
ManiphestBulkEditCapability::CAPABILITY);
|
||||||
|
|
||||||
$request = $this->getRequest();
|
$project = null;
|
||||||
$user = $request->getUser();
|
$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');
|
$task_ids = $request->getArr('batch');
|
||||||
|
if (!$task_ids) {
|
||||||
|
$task_ids = $request->getStrList('batch');
|
||||||
|
}
|
||||||
|
|
||||||
$tasks = id(new ManiphestTaskQuery())
|
$tasks = id(new ManiphestTaskQuery())
|
||||||
->setViewer($user)
|
->setViewer($viewer)
|
||||||
->withIDs($task_ids)
|
->withIDs($task_ids)
|
||||||
->requireCapabilities(
|
->requireCapabilities(
|
||||||
array(
|
array(
|
||||||
|
@ -22,6 +37,14 @@ final class ManiphestBatchEditController extends ManiphestController {
|
||||||
->needProjectPHIDs(true)
|
->needProjectPHIDs(true)
|
||||||
->execute();
|
->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');
|
$actions = $request->getStr('actions');
|
||||||
if ($actions) {
|
if ($actions) {
|
||||||
$actions = json_decode($actions, true);
|
$actions = json_decode($actions, true);
|
||||||
|
@ -39,7 +62,7 @@ final class ManiphestBatchEditController extends ManiphestController {
|
||||||
// TODO: Set content source to "batch edit".
|
// TODO: Set content source to "batch edit".
|
||||||
|
|
||||||
$editor = id(new ManiphestTransactionEditor())
|
$editor = id(new ManiphestTransactionEditor())
|
||||||
->setActor($user)
|
->setActor($viewer)
|
||||||
->setContentSourceFromRequest($request)
|
->setContentSourceFromRequest($request)
|
||||||
->setContinueOnNoEffect(true)
|
->setContinueOnNoEffect(true)
|
||||||
->setContinueOnMissingFields(true)
|
->setContinueOnMissingFields(true)
|
||||||
|
@ -47,17 +70,14 @@ final class ManiphestBatchEditController extends ManiphestController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$task_ids = implode(',', mpull($tasks, 'getID'));
|
return id(new AphrontRedirectResponse())->setURI($redirect_uri);
|
||||||
|
|
||||||
return id(new AphrontRedirectResponse())
|
|
||||||
->setURI('/maniphest/?ids='.$task_ids);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$handles = ManiphestTaskListView::loadTaskHandles($user, $tasks);
|
$handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks);
|
||||||
|
|
||||||
$list = new ManiphestTaskListView();
|
$list = new ManiphestTaskListView();
|
||||||
$list->setTasks($tasks);
|
$list->setTasks($tasks);
|
||||||
$list->setUser($user);
|
$list->setUser($viewer);
|
||||||
$list->setHandles($handles);
|
$list->setHandles($handles);
|
||||||
|
|
||||||
$template = new AphrontTokenizerTemplateView();
|
$template = new AphrontTokenizerTemplateView();
|
||||||
|
@ -65,9 +85,9 @@ final class ManiphestBatchEditController extends ManiphestController {
|
||||||
|
|
||||||
$projects_source = new PhabricatorProjectDatasource();
|
$projects_source = new PhabricatorProjectDatasource();
|
||||||
$mailable_source = new PhabricatorMetaMTAMailableDatasource();
|
$mailable_source = new PhabricatorMetaMTAMailableDatasource();
|
||||||
$mailable_source->setViewer($user);
|
$mailable_source->setViewer($viewer);
|
||||||
$owner_source = new PhabricatorTypeaheadOwnerDatasource();
|
$owner_source = new PhabricatorTypeaheadOwnerDatasource();
|
||||||
$owner_source->setViewer($user);
|
$owner_source->setViewer($viewer);
|
||||||
|
|
||||||
require_celerity_resource('maniphest-batch-editor');
|
require_celerity_resource('maniphest-batch-editor');
|
||||||
Javelin::initBehavior(
|
Javelin::initBehavior(
|
||||||
|
@ -98,9 +118,10 @@ final class ManiphestBatchEditController extends ManiphestController {
|
||||||
'statusMap' => ManiphestTaskStatus::getTaskStatusMap(),
|
'statusMap' => ManiphestTaskStatus::getTaskStatusMap(),
|
||||||
));
|
));
|
||||||
|
|
||||||
$form = new AphrontFormView();
|
$form = id(new AphrontFormView())
|
||||||
$form->setUser($user);
|
->setUser($viewer)
|
||||||
$form->setID('maniphest-batch-edit-form');
|
->addHiddenInput('board', $board_id)
|
||||||
|
->setID('maniphest-batch-edit-form');
|
||||||
|
|
||||||
foreach ($tasks as $task) {
|
foreach ($tasks as $task) {
|
||||||
$form->appendChild(
|
$form->appendChild(
|
||||||
|
@ -143,7 +164,7 @@ final class ManiphestBatchEditController extends ManiphestController {
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormSubmitControl())
|
id(new AphrontFormSubmitControl())
|
||||||
->setValue(pht('Update Tasks'))
|
->setValue(pht('Update Tasks'))
|
||||||
->addCancelButton('/maniphest/'));
|
->addCancelButton($cancel_uri));
|
||||||
|
|
||||||
$title = pht('Batch Editor');
|
$title = pht('Batch Editor');
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
final class PhabricatorProjectBoardViewController
|
final class PhabricatorProjectBoardViewController
|
||||||
extends PhabricatorProjectBoardController {
|
extends PhabricatorProjectBoardController {
|
||||||
|
|
||||||
|
const BATCH_EDIT_ALL = 'all';
|
||||||
|
|
||||||
private $id;
|
private $id;
|
||||||
private $slug;
|
private $slug;
|
||||||
private $handles;
|
private $handles;
|
||||||
|
@ -211,6 +213,50 @@ final class PhabricatorProjectBoardViewController
|
||||||
->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT))
|
->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT))
|
||||||
->apply($tasks);
|
->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 = celerity_generate_unique_node_id();
|
||||||
|
|
||||||
$board = id(new PHUIWorkboardView())
|
$board = id(new PHUIWorkboardView())
|
||||||
|
@ -518,6 +564,19 @@ final class PhabricatorProjectBoardViewController
|
||||||
->setName($hidden_text)
|
->setName($hidden_text)
|
||||||
->setHref($hidden_uri);
|
->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())
|
$manage_menu = id(new PhabricatorActionListView())
|
||||||
->setUser($viewer);
|
->setUser($viewer);
|
||||||
foreach ($manage_items as $item) {
|
foreach ($manage_items as $item) {
|
||||||
|
@ -563,6 +622,19 @@ final class PhabricatorProjectBoardViewController
|
||||||
))
|
))
|
||||||
->setDisabled(!$can_edit);
|
->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(
|
$edit_uri = $this->getApplicationURI(
|
||||||
'board/'.$this->id.'/column/'.$column->getID().'/');
|
'board/'.$this->id.'/column/'.$column->getID().'/');
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue