mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-25 22:18:19 +01:00
Move workboard "Bulk Edit Tasks" workflow to a separate controller
Summary: Depends on D20633. Ref T4900. Separate the "Bulk Edit Tasks..." flow out of the main workboard controller. Test Plan: - Used "Bulk Edit Tasks" on a column with some tasks, got an appropraite edit operation. - Used "Bulk Edit Tasks" on an empty column, got an error. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T4900 Differential Revision: https://secure.phabricator.com/D20634
This commit is contained in:
parent
9ea7227f0f
commit
ec352b1b31
5 changed files with 166 additions and 105 deletions
|
@ -4172,6 +4172,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectColorsConfigType' => 'applications/project/config/PhabricatorProjectColorsConfigType.php',
|
'PhabricatorProjectColorsConfigType' => 'applications/project/config/PhabricatorProjectColorsConfigType.php',
|
||||||
'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php',
|
'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php',
|
||||||
'PhabricatorProjectColumnAuthorOrder' => 'applications/project/order/PhabricatorProjectColumnAuthorOrder.php',
|
'PhabricatorProjectColumnAuthorOrder' => 'applications/project/order/PhabricatorProjectColumnAuthorOrder.php',
|
||||||
|
'PhabricatorProjectColumnBulkEditController' => 'applications/project/controller/PhabricatorProjectColumnBulkEditController.php',
|
||||||
'PhabricatorProjectColumnCreatedOrder' => 'applications/project/order/PhabricatorProjectColumnCreatedOrder.php',
|
'PhabricatorProjectColumnCreatedOrder' => 'applications/project/order/PhabricatorProjectColumnCreatedOrder.php',
|
||||||
'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php',
|
'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php',
|
||||||
'PhabricatorProjectColumnEditController' => 'applications/project/controller/PhabricatorProjectColumnEditController.php',
|
'PhabricatorProjectColumnEditController' => 'applications/project/controller/PhabricatorProjectColumnEditController.php',
|
||||||
|
@ -10445,6 +10446,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorConduitResultInterface',
|
'PhabricatorConduitResultInterface',
|
||||||
),
|
),
|
||||||
'PhabricatorProjectColumnAuthorOrder' => 'PhabricatorProjectColumnOrder',
|
'PhabricatorProjectColumnAuthorOrder' => 'PhabricatorProjectColumnOrder',
|
||||||
|
'PhabricatorProjectColumnBulkEditController' => 'PhabricatorProjectBoardController',
|
||||||
'PhabricatorProjectColumnCreatedOrder' => 'PhabricatorProjectColumnOrder',
|
'PhabricatorProjectColumnCreatedOrder' => 'PhabricatorProjectColumnOrder',
|
||||||
'PhabricatorProjectColumnDetailController' => 'PhabricatorProjectBoardController',
|
'PhabricatorProjectColumnDetailController' => 'PhabricatorProjectBoardController',
|
||||||
'PhabricatorProjectColumnEditController' => 'PhabricatorProjectBoardController',
|
'PhabricatorProjectColumnEditController' => 'PhabricatorProjectBoardController',
|
||||||
|
|
|
@ -81,6 +81,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
|
||||||
=> 'PhabricatorProjectColumnDetailController',
|
=> 'PhabricatorProjectColumnDetailController',
|
||||||
'viewquery/(?P<columnID>\d+)/'
|
'viewquery/(?P<columnID>\d+)/'
|
||||||
=> 'PhabricatorProjectColumnViewQueryController',
|
=> 'PhabricatorProjectColumnViewQueryController',
|
||||||
|
'bulk/(?P<columnID>\d+)/'
|
||||||
|
=> 'PhabricatorProjectColumnBulkEditController',
|
||||||
'import/'
|
'import/'
|
||||||
=> 'PhabricatorProjectBoardImportController',
|
=> 'PhabricatorProjectBoardImportController',
|
||||||
'reorder/'
|
'reorder/'
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
final class PhabricatorProjectBoardViewController
|
final class PhabricatorProjectBoardViewController
|
||||||
extends PhabricatorProjectBoardController {
|
extends PhabricatorProjectBoardController {
|
||||||
|
|
||||||
const BATCH_EDIT_ALL = 'all';
|
|
||||||
|
|
||||||
public function shouldAllowPublic() {
|
public function shouldAllowPublic() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -34,42 +32,9 @@ final class PhabricatorProjectBoardViewController
|
||||||
$custom_query = null;
|
$custom_query = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$task_query = $search_engine->buildQueryFromSavedQuery($saved);
|
$layout_engine = $state->getLayoutEngine();
|
||||||
|
|
||||||
$select_phids = array($project->getPHID());
|
|
||||||
if ($project->getHasSubprojects() || $project->getHasMilestones()) {
|
|
||||||
$descendants = id(new PhabricatorProjectQuery())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->withAncestorProjectPHIDs($select_phids)
|
|
||||||
->execute();
|
|
||||||
foreach ($descendants as $descendant) {
|
|
||||||
$select_phids[] = $descendant->getPHID();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$tasks = $task_query
|
|
||||||
->withEdgeLogicPHIDs(
|
|
||||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
|
||||||
PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
|
|
||||||
array($select_phids))
|
|
||||||
->setOrder(ManiphestTaskQuery::ORDER_PRIORITY)
|
|
||||||
->setViewer($viewer)
|
|
||||||
->execute();
|
|
||||||
$tasks = mpull($tasks, null, 'getPHID');
|
|
||||||
|
|
||||||
$board_phid = $project->getPHID();
|
$board_phid = $project->getPHID();
|
||||||
|
|
||||||
// Regardless of display order, pass tasks to the layout engine in ID order
|
|
||||||
// so layout is consistent.
|
|
||||||
$board_tasks = msort($tasks, 'getID');
|
|
||||||
|
|
||||||
$layout_engine = id(new PhabricatorBoardLayoutEngine())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->setBoardPHIDs(array($board_phid))
|
|
||||||
->setObjectPHIDs(array_keys($board_tasks))
|
|
||||||
->setFetchAllBoards(true)
|
|
||||||
->executeLayout();
|
|
||||||
|
|
||||||
$columns = $layout_engine->getColumns($board_phid);
|
$columns = $layout_engine->getColumns($board_phid);
|
||||||
if (!$columns || !$project->getHasWorkboard()) {
|
if (!$columns || !$project->getHasWorkboard()) {
|
||||||
$has_normal_columns = false;
|
$has_normal_columns = false;
|
||||||
|
@ -122,67 +87,13 @@ final class PhabricatorProjectBoardViewController
|
||||||
->appendChild($content);
|
->appendChild($content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$tasks = $state->getObjects();
|
||||||
|
|
||||||
$task_can_edit_map = id(new PhabricatorPolicyFilter())
|
$task_can_edit_map = id(new PhabricatorPolicyFilter())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->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 = $layout_engine->getColumnObjectPHIDs(
|
|
||||||
$board_phid,
|
|
||||||
$batch_column->getPHID());
|
|
||||||
|
|
||||||
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 = $state->newWorkboardURI();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a saved query to hold the working set. This allows us to get
|
|
||||||
// around URI length limitations with a long "?ids=..." query string.
|
|
||||||
// For details, see T10268.
|
|
||||||
$search_engine = id(new ManiphestTaskSearchEngine())
|
|
||||||
->setViewer($viewer);
|
|
||||||
|
|
||||||
$saved_query = $search_engine->newSavedQuery();
|
|
||||||
$saved_query->setParameter('ids', mpull($batch_tasks, 'getID'));
|
|
||||||
$search_engine->saveQuery($saved_query);
|
|
||||||
|
|
||||||
$query_key = $saved_query->getQueryKey();
|
|
||||||
|
|
||||||
$bulk_uri = new PhutilURI("/maniphest/bulk/query/{$query_key}/");
|
|
||||||
$bulk_uri->replaceQueryParam('board', $project->getID());
|
|
||||||
|
|
||||||
return id(new AphrontRedirectResponse())
|
|
||||||
->setURI($bulk_uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
$move_id = $request->getStr('move');
|
$move_id = $request->getStr('move');
|
||||||
if (strlen($move_id)) {
|
if (strlen($move_id)) {
|
||||||
$column_id_map = mpull($columns, null, 'getID');
|
$column_id_map = mpull($columns, null, 'getID');
|
||||||
|
@ -426,11 +337,13 @@ final class PhabricatorProjectBoardViewController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$container_phids = $state->getBoardContainerPHIDs();
|
||||||
|
|
||||||
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
|
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->setObjects(array_select_keys($tasks, $visible_phids))
|
->setObjects(array_select_keys($tasks, $visible_phids))
|
||||||
->setEditMap($task_can_edit_map)
|
->setEditMap($task_can_edit_map)
|
||||||
->setExcludedProjectPHIDs($select_phids);
|
->setExcludedProjectPHIDs($container_phids);
|
||||||
|
|
||||||
$templates = array();
|
$templates = array();
|
||||||
$all_tasks = array();
|
$all_tasks = array();
|
||||||
|
@ -912,13 +825,6 @@ final class PhabricatorProjectBoardViewController
|
||||||
->setName(pht('Manage Workboard'))
|
->setName(pht('Manage Workboard'))
|
||||||
->setHref($manage_uri);
|
->setHref($manage_uri);
|
||||||
|
|
||||||
$batch_edit_uri = $request->getRequestURI();
|
|
||||||
$batch_edit_uri->replaceQueryParam('batch', self::BATCH_EDIT_ALL);
|
|
||||||
$can_batch_edit = PhabricatorPolicyFilter::hasCapability(
|
|
||||||
$viewer,
|
|
||||||
PhabricatorApplication::getByClass('PhabricatorManiphestApplication'),
|
|
||||||
ManiphestBulkEditCapability::CAPABILITY);
|
|
||||||
|
|
||||||
$manage_menu = id(new PhabricatorActionListView())
|
$manage_menu = id(new PhabricatorActionListView())
|
||||||
->setUser($viewer);
|
->setUser($viewer);
|
||||||
foreach ($manage_items as $item) {
|
foreach ($manage_items as $item) {
|
||||||
|
@ -1002,9 +908,12 @@ final class PhabricatorProjectBoardViewController
|
||||||
$column_items[] = id(new PhabricatorActionView())
|
$column_items[] = id(new PhabricatorActionView())
|
||||||
->setType(PhabricatorActionView::TYPE_DIVIDER);
|
->setType(PhabricatorActionView::TYPE_DIVIDER);
|
||||||
|
|
||||||
$batch_edit_uri = $request->getRequestURI();
|
$bulk_edit_uri = $state->newWorkboardURI(
|
||||||
$batch_edit_uri->replaceQueryParam('batch', $column->getID());
|
urisprintf(
|
||||||
$can_batch_edit = PhabricatorPolicyFilter::hasCapability(
|
'bulk/%d/',
|
||||||
|
$column->getID()));
|
||||||
|
|
||||||
|
$can_bulk_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
$viewer,
|
$viewer,
|
||||||
PhabricatorApplication::getByClass('PhabricatorManiphestApplication'),
|
PhabricatorApplication::getByClass('PhabricatorManiphestApplication'),
|
||||||
ManiphestBulkEditCapability::CAPABILITY);
|
ManiphestBulkEditCapability::CAPABILITY);
|
||||||
|
@ -1012,8 +921,8 @@ final class PhabricatorProjectBoardViewController
|
||||||
$column_items[] = id(new PhabricatorActionView())
|
$column_items[] = id(new PhabricatorActionView())
|
||||||
->setIcon('fa-list-ul')
|
->setIcon('fa-list-ul')
|
||||||
->setName(pht('Bulk Edit Tasks...'))
|
->setName(pht('Bulk Edit Tasks...'))
|
||||||
->setHref($batch_edit_uri)
|
->setHref($bulk_edit_uri)
|
||||||
->setDisabled(!$can_batch_edit);
|
->setDisabled(!$can_bulk_edit);
|
||||||
|
|
||||||
$batch_move_uri = $request->getRequestURI();
|
$batch_move_uri = $request->getRequestURI();
|
||||||
$batch_move_uri->replaceQueryParam('move', $column->getID());
|
$batch_move_uri->replaceQueryParam('move', $column->getID());
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorProjectColumnBulkEditController
|
||||||
|
extends PhabricatorProjectBoardController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $request->getViewer();
|
||||||
|
|
||||||
|
$response = $this->loadProject();
|
||||||
|
if ($response) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$project = $this->getProject();
|
||||||
|
$state = $this->getViewState();
|
||||||
|
$board_uri = $state->newWorkboardURI();
|
||||||
|
|
||||||
|
$layout_engine = $state->getLayoutEngine();
|
||||||
|
|
||||||
|
$board_phid = $project->getPHID();
|
||||||
|
$columns = $layout_engine->getColumns($board_phid);
|
||||||
|
$columns = mpull($columns, null, 'getID');
|
||||||
|
|
||||||
|
$column_id = $request->getURIData('columnID');
|
||||||
|
$bulk_column = idx($columns, $column_id);
|
||||||
|
if (!$bulk_column) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulk_task_phids = $layout_engine->getColumnObjectPHIDs(
|
||||||
|
$board_phid,
|
||||||
|
$bulk_column->getPHID());
|
||||||
|
|
||||||
|
$tasks = $state->getObjects();
|
||||||
|
|
||||||
|
$bulk_tasks = array_select_keys($tasks, $bulk_task_phids);
|
||||||
|
|
||||||
|
$bulk_tasks = id(new PhabricatorPolicyFilter())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT))
|
||||||
|
->apply($bulk_tasks);
|
||||||
|
|
||||||
|
if (!$bulk_tasks) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a saved query to hold the working set. This allows us to get
|
||||||
|
// around URI length limitations with a long "?ids=..." query string.
|
||||||
|
// For details, see T10268.
|
||||||
|
$search_engine = id(new ManiphestTaskSearchEngine())
|
||||||
|
->setViewer($viewer);
|
||||||
|
|
||||||
|
$saved_query = $search_engine->newSavedQuery();
|
||||||
|
$saved_query->setParameter('ids', mpull($bulk_tasks, 'getID'));
|
||||||
|
$search_engine->saveQuery($saved_query);
|
||||||
|
|
||||||
|
$query_key = $saved_query->getQueryKey();
|
||||||
|
|
||||||
|
$bulk_uri = new PhutilURI("/maniphest/bulk/query/{$query_key}/");
|
||||||
|
$bulk_uri->replaceQueryParam('board', $project->getID());
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())
|
||||||
|
->setURI($bulk_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,6 +8,8 @@ final class PhabricatorWorkboardViewState
|
||||||
private $requestState = array();
|
private $requestState = array();
|
||||||
private $savedQuery;
|
private $savedQuery;
|
||||||
private $searchEngine;
|
private $searchEngine;
|
||||||
|
private $layoutEngine;
|
||||||
|
private $objects;
|
||||||
|
|
||||||
public function setProject(PhabricatorProject $project) {
|
public function setProject(PhabricatorProject $project) {
|
||||||
$this->project = $project;
|
$this->project = $project;
|
||||||
|
@ -212,4 +214,78 @@ final class PhabricatorWorkboardViewState
|
||||||
return $this->requestState;
|
return $this->requestState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getLayoutEngine() {
|
||||||
|
if ($this->layoutEngine === null) {
|
||||||
|
$this->layoutEngine = $this->newLayoutEngine();
|
||||||
|
}
|
||||||
|
return $this->layoutEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newLayoutEngine() {
|
||||||
|
$project = $this->getProject();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$board_phid = $project->getPHID();
|
||||||
|
$objects = $this->getObjects();
|
||||||
|
|
||||||
|
// Regardless of display order, pass tasks to the layout engine in ID order
|
||||||
|
// so layout is consistent.
|
||||||
|
$objects = msort($objects, 'getID');
|
||||||
|
|
||||||
|
$layout_engine = id(new PhabricatorBoardLayoutEngine())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setObjectPHIDs(array_keys($objects))
|
||||||
|
->setBoardPHIDs(array($board_phid))
|
||||||
|
->setFetchAllBoards(true)
|
||||||
|
->executeLayout();
|
||||||
|
|
||||||
|
return $layout_engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBoardContainerPHIDs() {
|
||||||
|
$project = $this->getProject();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$container_phids = array($project->getPHID());
|
||||||
|
if ($project->getHasSubprojects() || $project->getHasMilestones()) {
|
||||||
|
$descendants = id(new PhabricatorProjectQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withAncestorProjectPHIDs($container_phids)
|
||||||
|
->execute();
|
||||||
|
foreach ($descendants as $descendant) {
|
||||||
|
$container_phids[] = $descendant->getPHID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $container_phids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getObjects() {
|
||||||
|
if ($this->objects === null) {
|
||||||
|
$this->objects = $this->newObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newObjects() {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
$saved_query = $this->getSavedQuery();
|
||||||
|
$search_engine = $this->getSearchEngine();
|
||||||
|
|
||||||
|
$container_phids = $this->getBoardContainerPHIDs();
|
||||||
|
|
||||||
|
$task_query = $search_engine->buildQueryFromSavedQuery($saved_query)
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withEdgeLogicPHIDs(
|
||||||
|
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
||||||
|
PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
|
||||||
|
array($container_phids));
|
||||||
|
|
||||||
|
$tasks = $task_query->execute();
|
||||||
|
$tasks = mpull($tasks, null, 'getPHID');
|
||||||
|
|
||||||
|
return $tasks;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue