2011-02-08 10:53:59 -08:00
|
|
|
<?php
|
|
|
|
|
2011-07-04 13:04:22 -07:00
|
|
|
/**
|
|
|
|
* @group maniphest
|
|
|
|
*/
|
2012-03-09 15:46:25 -08:00
|
|
|
final class ManiphestTaskListController extends ManiphestController {
|
2011-02-08 10:53:59 -08:00
|
|
|
|
Allow Maniphest to scale to a massive size
Summary:
Maniphest is missing some keys and some query strategy which will make it
cumbersome to manage more than a few tens of thousands of tasks.
Test Plan:
Handily manipulated 100k-scale task groups. Maniphest takes about 250ms to
select and render pages of 1,000 tasks and has no problem paging and filtering
them, etc. We should be good to scale to multiple millions of tasks with these
changes.
Reviewed By: gc3
Reviewers: fratrik, jungejason, aran, tuomaspelkonen, gc3
Commenters: jungejason
CC: anjali, aran, epriestley, gc3, jungejason
Differential Revision: 534
2011-06-26 18:50:17 -07:00
|
|
|
const DEFAULT_PAGE_SIZE = 1000;
|
|
|
|
|
2011-02-08 10:53:59 -08:00
|
|
|
private $view;
|
|
|
|
|
|
|
|
public function willProcessRequest(array $data) {
|
|
|
|
$this->view = idx($data, 'view');
|
|
|
|
}
|
|
|
|
|
2012-02-28 21:08:02 -08:00
|
|
|
private function getArrToStrList($key) {
|
|
|
|
$arr = $this->getRequest()->getArr($key);
|
|
|
|
$arr = implode(',', $arr);
|
|
|
|
return nonempty($arr, null);
|
|
|
|
}
|
|
|
|
|
2011-02-08 10:53:59 -08:00
|
|
|
public function processRequest() {
|
|
|
|
|
2011-04-11 02:03:30 -07:00
|
|
|
$request = $this->getRequest();
|
|
|
|
$user = $request->getUser();
|
|
|
|
|
|
|
|
if ($request->isFormPost()) {
|
2011-06-29 16:16:33 -07:00
|
|
|
// Redirect to GET so URIs can be copy/pasted.
|
|
|
|
|
2011-12-02 07:30:20 -08:00
|
|
|
$task_ids = $request->getStr('set_tasks');
|
|
|
|
$task_ids = nonempty($task_ids, null);
|
2011-04-11 02:03:30 -07:00
|
|
|
|
2012-06-15 14:09:49 -07:00
|
|
|
$search_text = $request->getStr('set_search');
|
|
|
|
|
2012-07-31 22:52:46 -07:00
|
|
|
$min_priority = $request->getInt('set_lpriority');
|
|
|
|
|
|
|
|
$max_priority = $request->getInt('set_hpriority');
|
|
|
|
|
2011-06-29 16:16:33 -07:00
|
|
|
$uri = $request->getRequestURI()
|
2013-04-04 10:29:40 -07:00
|
|
|
->alter('users', $this->getArrToStrList('set_users'))
|
|
|
|
->alter('projects', $this->getArrToStrList('set_projects'))
|
|
|
|
->alter('aprojects', $this->getArrToStrList('set_aprojects'))
|
|
|
|
->alter('useraprojects', $this->getArrToStrList('set_useraprojects'))
|
|
|
|
->alter('xprojects', $this->getArrToStrList('set_xprojects'))
|
|
|
|
->alter('owners', $this->getArrToStrList('set_owners'))
|
|
|
|
->alter('authors', $this->getArrToStrList('set_authors'))
|
|
|
|
->alter('lpriority', $min_priority)
|
|
|
|
->alter('hpriority', $max_priority)
|
|
|
|
->alter('tasks', $task_ids)
|
|
|
|
->alter('search', $search_text);
|
2011-06-29 16:16:33 -07:00
|
|
|
|
|
|
|
return id(new AphrontRedirectResponse())->setURI($uri);
|
|
|
|
}
|
2011-04-11 02:03:30 -07:00
|
|
|
|
2012-03-01 14:19:11 -08:00
|
|
|
$nav = $this->buildBaseSideNav();
|
2011-12-20 14:36:54 -08:00
|
|
|
|
2011-04-11 02:03:30 -07:00
|
|
|
$has_filter = array(
|
|
|
|
'action' => true,
|
|
|
|
'created' => true,
|
2011-07-07 10:24:49 -07:00
|
|
|
'subscribed' => true,
|
2011-04-11 02:03:30 -07:00
|
|
|
'triage' => true,
|
2012-03-14 18:01:14 -07:00
|
|
|
'projecttriage' => true,
|
|
|
|
'projectall' => true,
|
2011-04-11 02:03:30 -07:00
|
|
|
);
|
2011-02-08 10:53:59 -08:00
|
|
|
|
2012-04-10 09:46:04 -07:00
|
|
|
$query = null;
|
|
|
|
$key = $request->getStr('key');
|
|
|
|
if (!$key && !$this->view) {
|
|
|
|
if ($this->getDefaultQuery()) {
|
|
|
|
$key = $this->getDefaultQuery()->getQueryKey();
|
|
|
|
}
|
|
|
|
}
|
2011-02-11 10:28:37 -08:00
|
|
|
|
2012-04-10 09:46:04 -07:00
|
|
|
if ($key) {
|
|
|
|
$query = id(new PhabricatorSearchQuery())->loadOneWhere(
|
|
|
|
'queryKey = %s',
|
|
|
|
$key);
|
2012-03-14 18:01:14 -07:00
|
|
|
}
|
2011-12-02 07:30:20 -08:00
|
|
|
|
2012-04-10 09:46:04 -07:00
|
|
|
// If the user is running a saved query, load query parameters from that
|
|
|
|
// query. Otherwise, build a new query object from the HTTP request.
|
Allow Maniphest to scale to a massive size
Summary:
Maniphest is missing some keys and some query strategy which will make it
cumbersome to manage more than a few tens of thousands of tasks.
Test Plan:
Handily manipulated 100k-scale task groups. Maniphest takes about 250ms to
select and render pages of 1,000 tasks and has no problem paging and filtering
them, etc. We should be good to scale to multiple millions of tasks with these
changes.
Reviewed By: gc3
Reviewers: fratrik, jungejason, aran, tuomaspelkonen, gc3
Commenters: jungejason
CC: anjali, aran, epriestley, gc3, jungejason
Differential Revision: 534
2011-06-26 18:50:17 -07:00
|
|
|
|
2012-04-10 09:46:04 -07:00
|
|
|
if ($query) {
|
|
|
|
$nav->selectFilter('Q:'.$query->getQueryKey(), 'custom');
|
|
|
|
$this->view = 'custom';
|
|
|
|
} else {
|
|
|
|
$this->view = $nav->selectFilter($this->view, 'action');
|
|
|
|
$query = $this->buildQueryFromRequest();
|
|
|
|
}
|
2011-02-08 10:53:59 -08:00
|
|
|
|
2012-04-10 09:46:04 -07:00
|
|
|
// Execute the query.
|
2012-02-28 21:07:12 -08:00
|
|
|
|
2013-02-28 17:15:09 -08:00
|
|
|
list($tasks, $handles, $total_count) = self::loadTasks(
|
|
|
|
$query,
|
|
|
|
$user);
|
2012-02-28 21:07:12 -08:00
|
|
|
|
2012-04-10 09:46:04 -07:00
|
|
|
// Extract information we need to render the filters from the query.
|
|
|
|
|
2012-06-15 14:09:49 -07:00
|
|
|
$search_text = $query->getParameter('fullTextSearch');
|
|
|
|
|
2012-04-10 09:46:04 -07:00
|
|
|
$user_phids = $query->getParameter('userPHIDs', array());
|
|
|
|
$task_ids = $query->getParameter('taskIDs', array());
|
|
|
|
$owner_phids = $query->getParameter('ownerPHIDs', array());
|
|
|
|
$author_phids = $query->getParameter('authorPHIDs', array());
|
|
|
|
$project_phids = $query->getParameter('projectPHIDs', array());
|
2012-10-04 15:31:14 -07:00
|
|
|
$any_project_phids = $query->getParameter(
|
|
|
|
'anyProjectPHIDs',
|
|
|
|
array());
|
2013-04-04 10:29:40 -07:00
|
|
|
$any_user_project_phids = $query->getParameter(
|
|
|
|
'anyUserProjectPHIDs',
|
|
|
|
array());
|
2012-04-10 09:46:04 -07:00
|
|
|
$exclude_project_phids = $query->getParameter(
|
|
|
|
'excludeProjectPHIDs',
|
|
|
|
array());
|
2012-07-31 22:52:46 -07:00
|
|
|
$low_priority = $query->getParameter('lowPriority');
|
|
|
|
$high_priority = $query->getParameter('highPriority');
|
|
|
|
|
2012-04-10 09:46:04 -07:00
|
|
|
$page_size = $query->getParameter('limit');
|
|
|
|
$page = $query->getParameter('offset');
|
|
|
|
|
|
|
|
$q_status = $query->getParameter('status');
|
|
|
|
$q_group = $query->getParameter('group');
|
|
|
|
$q_order = $query->getParameter('order');
|
|
|
|
|
2011-04-03 15:50:06 -07:00
|
|
|
$form = id(new AphrontFormView())
|
2011-06-29 16:16:33 -07:00
|
|
|
->setUser($user)
|
2012-04-10 09:46:04 -07:00
|
|
|
->setAction(
|
|
|
|
$request->getRequestURI()
|
|
|
|
->alter('key', null)
|
|
|
|
->alter(
|
|
|
|
$this->getStatusRequestKey(),
|
|
|
|
$this->getStatusRequestValue($q_status))
|
|
|
|
->alter(
|
|
|
|
$this->getOrderRequestKey(),
|
|
|
|
$this->getOrderRequestValue($q_order))
|
|
|
|
->alter(
|
|
|
|
$this->getGroupRequestKey(),
|
|
|
|
$this->getGroupRequestValue($q_group)));
|
2011-04-11 02:03:30 -07:00
|
|
|
|
|
|
|
if (isset($has_filter[$this->view])) {
|
2011-06-29 16:16:33 -07:00
|
|
|
$tokens = array();
|
|
|
|
foreach ($user_phids as $phid) {
|
|
|
|
$tokens[$phid] = $handles[$phid]->getFullName();
|
|
|
|
}
|
2011-04-11 02:03:30 -07:00
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTokenizerControl())
|
2011-05-28 14:13:12 -07:00
|
|
|
->setDatasource('/typeahead/common/searchowner/')
|
2011-06-29 16:16:33 -07:00
|
|
|
->setName('set_users')
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Users'))
|
2011-06-29 16:16:33 -07:00
|
|
|
->setValue($tokens));
|
|
|
|
}
|
|
|
|
|
2011-12-02 07:30:20 -08:00
|
|
|
if ($this->view == 'custom') {
|
2012-06-15 14:09:49 -07:00
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTextControl())
|
|
|
|
->setName('set_search')
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Search'))
|
2013-02-19 13:33:10 -08:00
|
|
|
->setValue($search_text));
|
2011-12-02 07:30:20 -08:00
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTextControl())
|
|
|
|
->setName('set_tasks')
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Task IDs'))
|
2013-02-19 13:33:10 -08:00
|
|
|
->setValue(join(',', $task_ids)));
|
2012-02-28 21:08:02 -08:00
|
|
|
|
|
|
|
$tokens = array();
|
|
|
|
foreach ($owner_phids as $phid) {
|
|
|
|
$tokens[$phid] = $handles[$phid]->getFullName();
|
|
|
|
}
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTokenizerControl())
|
|
|
|
->setDatasource('/typeahead/common/searchowner/')
|
|
|
|
->setName('set_owners')
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Owners'))
|
2012-02-28 21:08:02 -08:00
|
|
|
->setValue($tokens));
|
|
|
|
|
|
|
|
$tokens = array();
|
|
|
|
foreach ($author_phids as $phid) {
|
|
|
|
$tokens[$phid] = $handles[$phid]->getFullName();
|
|
|
|
}
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTokenizerControl())
|
2013-06-30 07:11:23 -07:00
|
|
|
->setDatasource('/typeahead/common/authors/')
|
2012-02-28 21:08:02 -08:00
|
|
|
->setName('set_authors')
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Authors'))
|
2012-02-28 21:08:02 -08:00
|
|
|
->setValue($tokens));
|
2011-12-02 07:30:20 -08:00
|
|
|
}
|
|
|
|
|
2011-06-29 16:16:33 -07:00
|
|
|
$tokens = array();
|
|
|
|
foreach ($project_phids as $phid) {
|
|
|
|
$tokens[$phid] = $handles[$phid]->getFullName();
|
2011-04-11 02:03:30 -07:00
|
|
|
}
|
2012-03-14 18:01:14 -07:00
|
|
|
if ($this->view != 'projectall' && $this->view != 'projecttriage') {
|
2012-10-04 15:31:14 -07:00
|
|
|
|
|
|
|
$caption = null;
|
|
|
|
if ($this->view == 'custom') {
|
2013-03-12 23:30:03 -07:00
|
|
|
$caption = pht('Find tasks in ALL of these projects ("AND" query).');
|
2012-10-04 15:31:14 -07:00
|
|
|
}
|
|
|
|
|
2012-03-14 18:01:14 -07:00
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTokenizerControl())
|
|
|
|
->setDatasource('/typeahead/common/searchproject/')
|
|
|
|
->setName('set_projects')
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Projects'))
|
2012-10-04 15:31:14 -07:00
|
|
|
->setCaption($caption)
|
2012-03-14 18:01:14 -07:00
|
|
|
->setValue($tokens));
|
|
|
|
}
|
2011-04-11 02:03:30 -07:00
|
|
|
|
2012-02-28 21:08:02 -08:00
|
|
|
if ($this->view == 'custom') {
|
2012-10-04 15:31:14 -07:00
|
|
|
$atokens = array();
|
|
|
|
foreach ($any_project_phids as $phid) {
|
|
|
|
$atokens[$phid] = $handles[$phid]->getFullName();
|
|
|
|
}
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTokenizerControl())
|
|
|
|
->setDatasource('/typeahead/common/projects/')
|
|
|
|
->setName('set_aprojects')
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Any Projects'))
|
|
|
|
->setCaption(pht('Find tasks in ANY of these projects ("OR" query).'))
|
2012-10-04 15:31:14 -07:00
|
|
|
->setValue($atokens));
|
|
|
|
|
2013-04-04 10:29:40 -07:00
|
|
|
$tokens = array();
|
|
|
|
foreach ($any_user_project_phids as $phid) {
|
|
|
|
$tokens[$phid] = $handles[$phid]->getFullName();
|
|
|
|
}
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTokenizerControl())
|
|
|
|
->setDatasource('/typeahead/common/users/')
|
|
|
|
->setName('set_useraprojects')
|
|
|
|
->setLabel(pht('Any User Projects'))
|
|
|
|
->setCaption(
|
|
|
|
pht('Find tasks in ANY of these users\' projects ("OR" query).'))
|
|
|
|
->setValue($tokens));
|
|
|
|
|
2012-02-28 21:08:02 -08:00
|
|
|
$tokens = array();
|
|
|
|
foreach ($exclude_project_phids as $phid) {
|
|
|
|
$tokens[$phid] = $handles[$phid]->getFullName();
|
|
|
|
}
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTokenizerControl())
|
|
|
|
->setDatasource('/typeahead/common/projects/')
|
|
|
|
->setName('set_xprojects')
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Exclude Projects'))
|
|
|
|
->setCaption(pht('Find tasks NOT in any of these projects.'))
|
2012-02-28 21:08:02 -08:00
|
|
|
->setValue($tokens));
|
2012-07-31 22:52:46 -07:00
|
|
|
|
|
|
|
$priority = ManiphestTaskPriority::getLowestPriority();
|
2012-11-12 13:53:33 -08:00
|
|
|
if ($low_priority !== null) {
|
2012-07-31 22:52:46 -07:00
|
|
|
$priority = $low_priority;
|
|
|
|
}
|
|
|
|
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormSelectControl())
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Min Priority'))
|
2012-07-31 22:52:46 -07:00
|
|
|
->setName('set_lpriority')
|
|
|
|
->setValue($priority)
|
|
|
|
->setOptions(array_reverse(
|
|
|
|
ManiphestTaskPriority::getTaskPriorityMap(), true)));
|
|
|
|
|
|
|
|
$priority = ManiphestTaskPriority::getHighestPriority();
|
2012-11-12 13:53:33 -08:00
|
|
|
if ($high_priority !== null) {
|
2012-07-31 22:52:46 -07:00
|
|
|
$priority = $high_priority;
|
|
|
|
}
|
|
|
|
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormSelectControl())
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Max Priority'))
|
2012-07-31 22:52:46 -07:00
|
|
|
->setName('set_hpriority')
|
|
|
|
->setValue($priority)
|
|
|
|
->setOptions(ManiphestTaskPriority::getTaskPriorityMap()));
|
|
|
|
|
2012-02-28 21:08:02 -08:00
|
|
|
}
|
|
|
|
|
2011-04-11 02:03:30 -07:00
|
|
|
$form
|
2012-04-10 09:46:04 -07:00
|
|
|
->appendChild($this->renderStatusControl($q_status))
|
|
|
|
->appendChild($this->renderGroupControl($q_group))
|
|
|
|
->appendChild($this->renderOrderControl($q_order));
|
|
|
|
|
|
|
|
$submit = id(new AphrontFormSubmitControl())
|
2013-03-12 23:30:03 -07:00
|
|
|
->setValue(pht('Filter Tasks'));
|
2012-04-10 09:46:04 -07:00
|
|
|
|
|
|
|
// Only show "Save..." for novel queries which have some kind of query
|
|
|
|
// parameters set.
|
|
|
|
if ($this->view === 'custom'
|
|
|
|
&& empty($key)
|
|
|
|
&& $request->getRequestURI()->getQueryParams()) {
|
|
|
|
$submit->addCancelButton(
|
|
|
|
'/maniphest/custom/edit/?key='.$query->getQueryKey(),
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('Save Custom Query...'));
|
2012-04-10 09:46:04 -07:00
|
|
|
}
|
2011-04-03 15:50:06 -07:00
|
|
|
|
2012-04-10 09:46:04 -07:00
|
|
|
$form->appendChild($submit);
|
2011-06-29 16:16:33 -07:00
|
|
|
|
2011-08-01 13:51:27 -07:00
|
|
|
$create_uri = new PhutilURI('/maniphest/task/create/');
|
|
|
|
if ($project_phids) {
|
|
|
|
// If we have project filters selected, use them as defaults for task
|
|
|
|
// creation.
|
|
|
|
$create_uri->setQueryParam('projects', implode(';', $project_phids));
|
|
|
|
}
|
|
|
|
|
2011-04-03 15:50:06 -07:00
|
|
|
$filter = new AphrontListFilterView();
|
2012-04-10 09:46:04 -07:00
|
|
|
if (empty($key)) {
|
|
|
|
$filter->appendChild($form);
|
|
|
|
}
|
2011-04-03 15:50:06 -07:00
|
|
|
|
2011-02-11 10:28:37 -08:00
|
|
|
$have_tasks = false;
|
|
|
|
foreach ($tasks as $group => $list) {
|
|
|
|
if (count($list)) {
|
|
|
|
$have_tasks = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-03 15:50:06 -07:00
|
|
|
require_celerity_resource('maniphest-task-summary-css');
|
|
|
|
|
2012-03-05 13:51:35 -08:00
|
|
|
$list_container = new AphrontNullView();
|
2013-02-13 14:50:15 -08:00
|
|
|
$list_container->appendChild(hsprintf(
|
|
|
|
'<div class="maniphest-list-container">'));
|
2012-03-05 13:51:35 -08:00
|
|
|
|
2011-02-11 10:28:37 -08:00
|
|
|
if (!$have_tasks) {
|
2013-03-12 23:30:03 -07:00
|
|
|
$no_tasks = pht('No matching tasks.');
|
2013-02-13 14:50:15 -08:00
|
|
|
$list_container->appendChild(hsprintf(
|
2011-02-11 10:28:37 -08:00
|
|
|
'<h1 class="maniphest-task-group-header">'.
|
2013-03-12 23:30:03 -07:00
|
|
|
'%s'.
|
|
|
|
'</h1>',
|
|
|
|
$no_tasks));
|
2013-03-13 08:05:44 -07:00
|
|
|
$result_count = null;
|
2011-02-11 10:28:37 -08:00
|
|
|
} else {
|
Allow Maniphest to scale to a massive size
Summary:
Maniphest is missing some keys and some query strategy which will make it
cumbersome to manage more than a few tens of thousands of tasks.
Test Plan:
Handily manipulated 100k-scale task groups. Maniphest takes about 250ms to
select and render pages of 1,000 tasks and has no problem paging and filtering
them, etc. We should be good to scale to multiple millions of tasks with these
changes.
Reviewed By: gc3
Reviewers: fratrik, jungejason, aran, tuomaspelkonen, gc3
Commenters: jungejason
CC: anjali, aran, epriestley, gc3, jungejason
Differential Revision: 534
2011-06-26 18:50:17 -07:00
|
|
|
$pager = new AphrontPagerView();
|
2012-04-10 09:46:04 -07:00
|
|
|
$pager->setURI($request->getRequestURI(), 'offset');
|
Allow Maniphest to scale to a massive size
Summary:
Maniphest is missing some keys and some query strategy which will make it
cumbersome to manage more than a few tens of thousands of tasks.
Test Plan:
Handily manipulated 100k-scale task groups. Maniphest takes about 250ms to
select and render pages of 1,000 tasks and has no problem paging and filtering
them, etc. We should be good to scale to multiple millions of tasks with these
changes.
Reviewed By: gc3
Reviewers: fratrik, jungejason, aran, tuomaspelkonen, gc3
Commenters: jungejason
CC: anjali, aran, epriestley, gc3, jungejason
Differential Revision: 534
2011-06-26 18:50:17 -07:00
|
|
|
$pager->setPageSize($page_size);
|
|
|
|
$pager->setOffset($page);
|
|
|
|
$pager->setCount($total_count);
|
|
|
|
|
|
|
|
$cur = ($pager->getOffset() + 1);
|
|
|
|
$max = min($pager->getOffset() + $page_size, $total_count);
|
|
|
|
$tot = $total_count;
|
|
|
|
|
2013-03-12 23:30:03 -07:00
|
|
|
$results = pht('Displaying tasks %s - %s of %s.',
|
2013-02-13 14:50:15 -08:00
|
|
|
number_format($cur),
|
|
|
|
number_format($max),
|
2013-03-12 23:30:03 -07:00
|
|
|
number_format($tot));
|
|
|
|
$result_count = phutil_tag(
|
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => 'maniphest-total-result-count'
|
|
|
|
),
|
|
|
|
$results);
|
Allow Maniphest to scale to a massive size
Summary:
Maniphest is missing some keys and some query strategy which will make it
cumbersome to manage more than a few tens of thousands of tasks.
Test Plan:
Handily manipulated 100k-scale task groups. Maniphest takes about 250ms to
select and render pages of 1,000 tasks and has no problem paging and filtering
them, etc. We should be good to scale to multiple millions of tasks with these
changes.
Reviewed By: gc3
Reviewers: fratrik, jungejason, aran, tuomaspelkonen, gc3
Commenters: jungejason
CC: anjali, aran, epriestley, gc3, jungejason
Differential Revision: 534
2011-06-26 18:50:17 -07:00
|
|
|
|
2012-02-24 13:00:48 -08:00
|
|
|
$selector = new AphrontNullView();
|
|
|
|
|
2012-04-02 12:12:04 -07:00
|
|
|
$group = $query->getParameter('group');
|
|
|
|
$order = $query->getParameter('order');
|
|
|
|
$is_draggable =
|
2013-02-04 18:59:46 -08:00
|
|
|
($order == 'priority') &&
|
|
|
|
($group == 'none' || $group == 'priority');
|
2012-04-02 12:12:04 -07:00
|
|
|
|
2013-03-12 23:30:03 -07:00
|
|
|
$lists = array();
|
2011-02-11 10:28:37 -08:00
|
|
|
foreach ($tasks as $group => $list) {
|
|
|
|
$task_list = new ManiphestTaskListView();
|
2012-02-24 13:00:48 -08:00
|
|
|
$task_list->setShowBatchControls(true);
|
2012-04-02 12:12:04 -07:00
|
|
|
if ($is_draggable) {
|
|
|
|
$task_list->setShowSubpriorityControls(true);
|
|
|
|
}
|
Use phabricator_ time functions in more places
Summary:
Replace some more date() calls with locale-aware calls.
Also, at least on my system, the DateTimeZone / DateTime stuff didn't actually
work and always rendered in UTC. Fixed that.
Test Plan:
Viewed daemon console, differential revisions, files, and maniphest timestamps
in multiple timezones.
Reviewed By: toulouse
Reviewers: toulouse, fratrik, jungejason, aran, tuomaspelkonen
CC: aran, toulouse
Differential Revision: 530
2011-06-26 09:22:52 -07:00
|
|
|
$task_list->setUser($user);
|
2011-02-11 10:28:37 -08:00
|
|
|
$task_list->setTasks($list);
|
|
|
|
$task_list->setHandles($handles);
|
|
|
|
|
2011-06-13 20:03:44 -07:00
|
|
|
$count = number_format(count($list));
|
2012-04-02 12:12:04 -07:00
|
|
|
|
2013-01-18 18:19:56 -08:00
|
|
|
$header =
|
2013-01-28 18:46:48 -08:00
|
|
|
javelin_tag(
|
2012-04-02 12:12:04 -07:00
|
|
|
'h1',
|
|
|
|
array(
|
|
|
|
'class' => 'maniphest-task-group-header',
|
|
|
|
'sigil' => 'task-group',
|
|
|
|
'meta' => array(
|
|
|
|
'priority' => head($list)->getPriority(),
|
|
|
|
),
|
|
|
|
),
|
2013-01-28 18:46:48 -08:00
|
|
|
$group.' ('.$count.')');
|
2012-04-02 12:12:04 -07:00
|
|
|
|
2013-03-12 23:30:03 -07:00
|
|
|
$lists[] =
|
|
|
|
phutil_tag(
|
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => 'maniphest-task-group'
|
|
|
|
),
|
|
|
|
array(
|
|
|
|
$header,
|
|
|
|
$task_list,
|
|
|
|
));
|
2011-02-11 10:28:37 -08:00
|
|
|
}
|
2012-02-24 13:00:48 -08:00
|
|
|
|
2013-03-12 23:30:03 -07:00
|
|
|
$selector->appendChild($lists);
|
2012-02-28 21:07:12 -08:00
|
|
|
$selector->appendChild($this->renderBatchEditor($query));
|
2012-02-24 13:00:48 -08:00
|
|
|
|
2012-03-05 13:51:35 -08:00
|
|
|
$list_container->appendChild($selector);
|
|
|
|
$list_container->appendChild($pager);
|
2012-04-02 12:12:04 -07:00
|
|
|
|
|
|
|
Javelin::initBehavior(
|
|
|
|
'maniphest-subpriority-editor',
|
|
|
|
array(
|
|
|
|
'uri' => '/maniphest/subpriority/',
|
|
|
|
));
|
2011-02-11 10:28:37 -08:00
|
|
|
}
|
2011-02-08 10:53:59 -08:00
|
|
|
|
2013-03-12 23:30:03 -07:00
|
|
|
$nav->appendChild($filter);
|
|
|
|
$nav->appendChild($result_count);
|
2012-03-05 13:51:35 -08:00
|
|
|
$nav->appendChild($list_container);
|
|
|
|
|
2013-01-15 15:38:05 -08:00
|
|
|
$title = pht('Task List');
|
|
|
|
|
2013-01-17 08:33:34 -08:00
|
|
|
$crumbs = $this->buildApplicationCrumbs()
|
2013-01-15 15:38:05 -08:00
|
|
|
->addCrumb(
|
|
|
|
id(new PhabricatorCrumbView())
|
|
|
|
->setName($title))
|
|
|
|
->addAction(
|
2013-06-05 08:41:43 -07:00
|
|
|
id(new PHUIListItemView())
|
2013-01-15 15:38:05 -08:00
|
|
|
->setHref($this->getApplicationURI('/task/create/'))
|
|
|
|
->setName(pht('Create Task'))
|
|
|
|
->setIcon('create'));
|
|
|
|
|
|
|
|
$nav->setCrumbs($crumbs);
|
|
|
|
|
2013-03-12 23:30:03 -07:00
|
|
|
return $this->buildApplicationPage(
|
2011-02-08 10:53:59 -08:00
|
|
|
$nav,
|
|
|
|
array(
|
2013-01-15 15:38:05 -08:00
|
|
|
'title' => $title,
|
2013-03-12 23:30:03 -07:00
|
|
|
'device' => true,
|
2011-02-08 10:53:59 -08:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2013-02-28 17:15:09 -08:00
|
|
|
public static function loadTasks(
|
|
|
|
PhabricatorSearchQuery $search_query,
|
|
|
|
PhabricatorUser $viewer) {
|
|
|
|
|
2012-03-30 10:55:18 -07:00
|
|
|
$any_project = false;
|
2012-06-15 14:09:49 -07:00
|
|
|
$search_text = $search_query->getParameter('fullTextSearch');
|
2012-02-28 21:07:12 -08:00
|
|
|
$user_phids = $search_query->getParameter('userPHIDs', array());
|
|
|
|
$task_ids = $search_query->getParameter('taskIDs', array());
|
2012-10-04 15:30:51 -07:00
|
|
|
$project_phids = $search_query->getParameter('projectPHIDs', array());
|
|
|
|
$any_project_phids = $search_query->getParameter(
|
|
|
|
'anyProjectPHIDs',
|
|
|
|
array());
|
2013-04-04 10:29:40 -07:00
|
|
|
$any_user_project_phids = $search_query->getParameter(
|
|
|
|
'anyUserProjectPHIDs',
|
|
|
|
array());
|
2012-02-28 21:08:02 -08:00
|
|
|
$xproject_phids = $search_query->getParameter(
|
|
|
|
'excludeProjectPHIDs',
|
|
|
|
array());
|
|
|
|
$owner_phids = $search_query->getParameter('ownerPHIDs', array());
|
|
|
|
$author_phids = $search_query->getParameter('authorPHIDs', array());
|
2011-02-08 10:53:59 -08:00
|
|
|
|
2012-07-31 22:52:46 -07:00
|
|
|
$low_priority = $search_query->getParameter('lowPriority');
|
2012-11-12 13:53:33 -08:00
|
|
|
$low_priority = coalesce($low_priority,
|
2012-07-31 22:52:46 -07:00
|
|
|
ManiphestTaskPriority::getLowestPriority());
|
|
|
|
$high_priority = $search_query->getParameter('highPriority');
|
2012-11-12 13:53:33 -08:00
|
|
|
$high_priority = coalesce($high_priority,
|
2012-07-31 22:52:46 -07:00
|
|
|
ManiphestTaskPriority::getHighestPriority());
|
|
|
|
|
2011-06-29 16:16:33 -07:00
|
|
|
$query = new ManiphestTaskQuery();
|
2013-09-10 07:44:22 -07:00
|
|
|
$query->setViewer($viewer);
|
2013-09-10 07:47:55 -07:00
|
|
|
$query->withIDs($task_ids);
|
2011-02-11 10:28:37 -08:00
|
|
|
|
2012-10-04 15:31:04 -07:00
|
|
|
if ($project_phids) {
|
|
|
|
$query->withAllProjects($project_phids);
|
|
|
|
}
|
|
|
|
|
2012-02-28 21:08:02 -08:00
|
|
|
if ($xproject_phids) {
|
|
|
|
$query->withoutProjects($xproject_phids);
|
|
|
|
}
|
|
|
|
|
2012-10-04 15:31:14 -07:00
|
|
|
if ($any_project_phids) {
|
|
|
|
$query->withAnyProjects($any_project_phids);
|
|
|
|
}
|
|
|
|
|
2012-02-28 21:08:02 -08:00
|
|
|
if ($owner_phids) {
|
|
|
|
$query->withOwners($owner_phids);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($author_phids) {
|
|
|
|
$query->withAuthors($author_phids);
|
|
|
|
}
|
|
|
|
|
2013-04-04 10:29:40 -07:00
|
|
|
if ($any_user_project_phids) {
|
|
|
|
$query->setViewer($viewer);
|
|
|
|
$query->withAnyUserProjects($any_user_project_phids);
|
|
|
|
}
|
|
|
|
|
2012-02-28 21:07:12 -08:00
|
|
|
$status = $search_query->getParameter('status', 'all');
|
2011-02-11 10:28:37 -08:00
|
|
|
if (!empty($status['open']) && !empty($status['closed'])) {
|
2011-06-29 16:16:33 -07:00
|
|
|
$query->withStatus(ManiphestTaskQuery::STATUS_ANY);
|
2011-02-11 10:28:37 -08:00
|
|
|
} else if (!empty($status['open'])) {
|
2011-06-29 16:16:33 -07:00
|
|
|
$query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
|
2011-02-11 10:28:37 -08:00
|
|
|
} else {
|
2011-06-29 16:16:33 -07:00
|
|
|
$query->withStatus(ManiphestTaskQuery::STATUS_CLOSED);
|
2011-02-11 10:28:37 -08:00
|
|
|
}
|
|
|
|
|
2012-02-28 21:07:12 -08:00
|
|
|
switch ($search_query->getParameter('view')) {
|
2011-02-08 10:53:59 -08:00
|
|
|
case 'action':
|
2011-06-29 16:16:33 -07:00
|
|
|
$query->withOwners($user_phids);
|
2011-02-11 10:28:37 -08:00
|
|
|
break;
|
2011-02-08 10:53:59 -08:00
|
|
|
case 'created':
|
2011-06-29 16:16:33 -07:00
|
|
|
$query->withAuthors($user_phids);
|
2011-02-11 10:28:37 -08:00
|
|
|
break;
|
2011-07-07 10:24:49 -07:00
|
|
|
case 'subscribed':
|
|
|
|
$query->withSubscribers($user_phids);
|
|
|
|
break;
|
2011-02-08 10:53:59 -08:00
|
|
|
case 'triage':
|
2011-06-29 16:16:33 -07:00
|
|
|
$query->withOwners($user_phids);
|
|
|
|
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
|
2011-02-11 10:28:37 -08:00
|
|
|
break;
|
2011-02-09 16:29:46 -08:00
|
|
|
case 'alltriage':
|
2011-06-29 16:16:33 -07:00
|
|
|
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
|
2011-02-11 10:28:37 -08:00
|
|
|
break;
|
|
|
|
case 'all':
|
|
|
|
break;
|
2012-03-14 18:01:14 -07:00
|
|
|
case 'projecttriage':
|
|
|
|
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
|
|
|
|
break;
|
|
|
|
case 'projectall':
|
|
|
|
break;
|
2012-07-31 22:52:46 -07:00
|
|
|
case 'custom':
|
|
|
|
$query->withPrioritiesBetween($low_priority, $high_priority);
|
|
|
|
break;
|
2011-02-11 10:28:37 -08:00
|
|
|
}
|
|
|
|
|
2012-06-15 14:09:49 -07:00
|
|
|
$query->withFullTextSearch($search_text);
|
|
|
|
|
2011-06-29 16:16:33 -07:00
|
|
|
$order_map = array(
|
|
|
|
'priority' => ManiphestTaskQuery::ORDER_PRIORITY,
|
|
|
|
'created' => ManiphestTaskQuery::ORDER_CREATED,
|
2012-08-02 14:21:13 -07:00
|
|
|
'title' => ManiphestTaskQuery::ORDER_TITLE,
|
2011-06-29 16:16:33 -07:00
|
|
|
);
|
|
|
|
$query->setOrderBy(
|
|
|
|
idx(
|
|
|
|
$order_map,
|
2012-02-28 21:07:12 -08:00
|
|
|
$search_query->getParameter('order'),
|
2011-06-29 16:16:33 -07:00
|
|
|
ManiphestTaskQuery::ORDER_MODIFIED));
|
|
|
|
|
|
|
|
$group_map = array(
|
|
|
|
'priority' => ManiphestTaskQuery::GROUP_PRIORITY,
|
|
|
|
'owner' => ManiphestTaskQuery::GROUP_OWNER,
|
|
|
|
'status' => ManiphestTaskQuery::GROUP_STATUS,
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
'project' => ManiphestTaskQuery::GROUP_PROJECT,
|
2011-06-29 16:16:33 -07:00
|
|
|
);
|
|
|
|
$query->setGroupBy(
|
|
|
|
idx(
|
|
|
|
$group_map,
|
2012-02-28 21:07:12 -08:00
|
|
|
$search_query->getParameter('group'),
|
2011-06-29 16:16:33 -07:00
|
|
|
ManiphestTaskQuery::GROUP_NONE));
|
Allow Maniphest to scale to a massive size
Summary:
Maniphest is missing some keys and some query strategy which will make it
cumbersome to manage more than a few tens of thousands of tasks.
Test Plan:
Handily manipulated 100k-scale task groups. Maniphest takes about 250ms to
select and render pages of 1,000 tasks and has no problem paging and filtering
them, etc. We should be good to scale to multiple millions of tasks with these
changes.
Reviewed By: gc3
Reviewers: fratrik, jungejason, aran, tuomaspelkonen, gc3
Commenters: jungejason
CC: anjali, aran, epriestley, gc3, jungejason
Differential Revision: 534
2011-06-26 18:50:17 -07:00
|
|
|
|
2011-06-29 16:16:33 -07:00
|
|
|
$query->setCalculateRows(true);
|
2012-02-28 21:07:12 -08:00
|
|
|
$query->setLimit($search_query->getParameter('limit'));
|
|
|
|
$query->setOffset($search_query->getParameter('offset'));
|
Allow Maniphest to scale to a massive size
Summary:
Maniphest is missing some keys and some query strategy which will make it
cumbersome to manage more than a few tens of thousands of tasks.
Test Plan:
Handily manipulated 100k-scale task groups. Maniphest takes about 250ms to
select and render pages of 1,000 tasks and has no problem paging and filtering
them, etc. We should be good to scale to multiple millions of tasks with these
changes.
Reviewed By: gc3
Reviewers: fratrik, jungejason, aran, tuomaspelkonen, gc3
Commenters: jungejason
CC: anjali, aran, epriestley, gc3, jungejason
Differential Revision: 534
2011-06-26 18:50:17 -07:00
|
|
|
|
2011-06-29 16:16:33 -07:00
|
|
|
$data = $query->execute();
|
|
|
|
$total_row_count = $query->getRowCount();
|
2011-02-11 10:28:37 -08:00
|
|
|
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
$project_group_phids = array();
|
|
|
|
if ($search_query->getParameter('group') == 'project') {
|
|
|
|
foreach ($data as $task) {
|
|
|
|
foreach ($task->getProjectPHIDs() as $phid) {
|
|
|
|
$project_group_phids[] = $phid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-11 10:28:37 -08:00
|
|
|
$handle_phids = mpull($data, 'getOwnerPHID');
|
2012-02-28 21:08:02 -08:00
|
|
|
$handle_phids = array_merge(
|
|
|
|
$handle_phids,
|
|
|
|
$project_phids,
|
|
|
|
$user_phids,
|
|
|
|
$xproject_phids,
|
|
|
|
$owner_phids,
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
$author_phids,
|
2012-04-02 10:27:31 -07:00
|
|
|
$project_group_phids,
|
2012-11-08 09:05:38 -08:00
|
|
|
$any_project_phids,
|
2013-06-20 09:25:00 -07:00
|
|
|
$any_user_project_phids,
|
2012-04-02 10:27:31 -07:00
|
|
|
array_mergev(mpull($data, 'getProjectPHIDs')));
|
2011-02-11 10:28:37 -08:00
|
|
|
$handles = id(new PhabricatorObjectHandleData($handle_phids))
|
2013-02-28 17:15:09 -08:00
|
|
|
->setViewer($viewer)
|
2011-02-11 10:28:37 -08:00
|
|
|
->loadHandles();
|
|
|
|
|
2012-02-28 21:07:12 -08:00
|
|
|
switch ($search_query->getParameter('group')) {
|
2011-02-11 10:28:37 -08:00
|
|
|
case 'priority':
|
|
|
|
$data = mgroup($data, 'getPriority');
|
|
|
|
|
Allow Maniphest to scale to a massive size
Summary:
Maniphest is missing some keys and some query strategy which will make it
cumbersome to manage more than a few tens of thousands of tasks.
Test Plan:
Handily manipulated 100k-scale task groups. Maniphest takes about 250ms to
select and render pages of 1,000 tasks and has no problem paging and filtering
them, etc. We should be good to scale to multiple millions of tasks with these
changes.
Reviewed By: gc3
Reviewers: fratrik, jungejason, aran, tuomaspelkonen, gc3
Commenters: jungejason
CC: anjali, aran, epriestley, gc3, jungejason
Differential Revision: 534
2011-06-26 18:50:17 -07:00
|
|
|
// If we have invalid priorities, they'll all map to "???". Merge
|
|
|
|
// arrays to prevent them from overwriting each other.
|
|
|
|
|
2011-02-11 10:28:37 -08:00
|
|
|
$out = array();
|
|
|
|
foreach ($data as $pri => $tasks) {
|
Allow Maniphest to scale to a massive size
Summary:
Maniphest is missing some keys and some query strategy which will make it
cumbersome to manage more than a few tens of thousands of tasks.
Test Plan:
Handily manipulated 100k-scale task groups. Maniphest takes about 250ms to
select and render pages of 1,000 tasks and has no problem paging and filtering
them, etc. We should be good to scale to multiple millions of tasks with these
changes.
Reviewed By: gc3
Reviewers: fratrik, jungejason, aran, tuomaspelkonen, gc3
Commenters: jungejason
CC: anjali, aran, epriestley, gc3, jungejason
Differential Revision: 534
2011-06-26 18:50:17 -07:00
|
|
|
$out[ManiphestTaskPriority::getTaskPriorityName($pri)][] = $tasks;
|
|
|
|
}
|
|
|
|
foreach ($out as $pri => $tasks) {
|
|
|
|
$out[$pri] = array_mergev($tasks);
|
2011-02-11 10:28:37 -08:00
|
|
|
}
|
|
|
|
$data = $out;
|
|
|
|
|
|
|
|
break;
|
|
|
|
case 'status':
|
|
|
|
$data = mgroup($data, 'getStatus');
|
|
|
|
|
|
|
|
$out = array();
|
|
|
|
foreach ($data as $status => $tasks) {
|
|
|
|
$out[ManiphestTaskStatus::getTaskStatusFullName($status)] = $tasks;
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = $out;
|
|
|
|
break;
|
|
|
|
case 'owner':
|
|
|
|
$data = mgroup($data, 'getOwnerPHID');
|
|
|
|
|
|
|
|
$out = array();
|
|
|
|
foreach ($data as $phid => $tasks) {
|
|
|
|
if ($phid) {
|
|
|
|
$out[$handles[$phid]->getFullName()] = $tasks;
|
|
|
|
} else {
|
|
|
|
$out['Unassigned'] = $tasks;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-22 13:55:22 -07:00
|
|
|
$data = $out;
|
2011-02-11 10:28:37 -08:00
|
|
|
ksort($data);
|
2012-03-22 13:55:22 -07:00
|
|
|
|
|
|
|
// Move "Unassigned" to the top of the list.
|
|
|
|
if (isset($data['Unassigned'])) {
|
|
|
|
$data = array('Unassigned' => $out['Unassigned']) + $out;
|
|
|
|
}
|
2011-02-11 10:28:37 -08:00
|
|
|
break;
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
case 'project':
|
|
|
|
$grouped = array();
|
2012-08-07 19:40:38 -07:00
|
|
|
foreach ($query->getGroupByProjectResults() as $project => $tasks) {
|
|
|
|
foreach ($tasks as $task) {
|
|
|
|
$group = $project ? $handles[$project]->getName() : 'No Project';
|
|
|
|
$grouped[$group][$task->getID()] = $task;
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
$data = $grouped;
|
2012-03-22 13:55:22 -07:00
|
|
|
ksort($data);
|
|
|
|
|
|
|
|
// Move "No Project" to the end of the list.
|
|
|
|
if (isset($data['No Project'])) {
|
|
|
|
$noproject = $data['No Project'];
|
|
|
|
unset($data['No Project']);
|
|
|
|
$data += array('No Project' => $noproject);
|
|
|
|
}
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
break;
|
2011-02-11 10:28:37 -08:00
|
|
|
default:
|
|
|
|
$data = array(
|
|
|
|
'Tasks' => $data,
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
Allow Maniphest to scale to a massive size
Summary:
Maniphest is missing some keys and some query strategy which will make it
cumbersome to manage more than a few tens of thousands of tasks.
Test Plan:
Handily manipulated 100k-scale task groups. Maniphest takes about 250ms to
select and render pages of 1,000 tasks and has no problem paging and filtering
them, etc. We should be good to scale to multiple millions of tasks with these
changes.
Reviewed By: gc3
Reviewers: fratrik, jungejason, aran, tuomaspelkonen, gc3
Commenters: jungejason
CC: anjali, aran, epriestley, gc3, jungejason
Differential Revision: 534
2011-06-26 18:50:17 -07:00
|
|
|
return array($data, $handles, $total_row_count);
|
2011-02-11 10:28:37 -08:00
|
|
|
}
|
|
|
|
|
2012-02-28 21:07:12 -08:00
|
|
|
private function renderBatchEditor(PhabricatorSearchQuery $search_query) {
|
2013-03-23 14:38:01 -07:00
|
|
|
$user = $this->getRequest()->getUser();
|
|
|
|
|
2012-02-24 13:00:48 -08:00
|
|
|
Javelin::initBehavior(
|
|
|
|
'maniphest-batch-selector',
|
|
|
|
array(
|
|
|
|
'selectAll' => 'batch-select-all',
|
|
|
|
'selectNone' => 'batch-select-none',
|
|
|
|
'submit' => 'batch-select-submit',
|
|
|
|
'status' => 'batch-select-status-cell',
|
2013-03-23 14:38:01 -07:00
|
|
|
'idContainer' => 'batch-select-id-container',
|
|
|
|
'formID' => 'batch-select-form',
|
2012-02-24 13:00:48 -08:00
|
|
|
));
|
|
|
|
|
2013-01-25 12:57:17 -08:00
|
|
|
$select_all = javelin_tag(
|
2012-02-24 13:00:48 -08:00
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => '#',
|
|
|
|
'mustcapture' => true,
|
|
|
|
'class' => 'grey button',
|
|
|
|
'id' => 'batch-select-all',
|
|
|
|
),
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('Select All'));
|
2012-02-24 13:00:48 -08:00
|
|
|
|
2013-01-25 12:57:17 -08:00
|
|
|
$select_none = javelin_tag(
|
2012-02-24 13:00:48 -08:00
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => '#',
|
|
|
|
'mustcapture' => true,
|
|
|
|
'class' => 'grey button',
|
|
|
|
'id' => 'batch-select-none',
|
|
|
|
),
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('Clear Selection'));
|
2012-02-24 13:00:48 -08:00
|
|
|
|
2013-01-17 18:57:09 -08:00
|
|
|
$submit = phutil_tag(
|
2012-02-24 13:00:48 -08:00
|
|
|
'button',
|
|
|
|
array(
|
|
|
|
'id' => 'batch-select-submit',
|
|
|
|
'disabled' => 'disabled',
|
|
|
|
'class' => 'disabled',
|
|
|
|
),
|
2013-03-12 23:30:03 -07:00
|
|
|
pht("Batch Edit Selected \xC2\xBB"));
|
2012-02-24 13:00:48 -08:00
|
|
|
|
2013-01-25 12:57:17 -08:00
|
|
|
$export = javelin_tag(
|
2012-02-28 21:07:12 -08:00
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => '/maniphest/export/'.$search_query->getQueryKey().'/',
|
|
|
|
'class' => 'grey button',
|
|
|
|
),
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('Export to Excel'));
|
2012-02-28 21:07:12 -08:00
|
|
|
|
2013-03-23 14:38:01 -07:00
|
|
|
$hidden = phutil_tag(
|
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'id' => 'batch-select-id-container',
|
|
|
|
),
|
|
|
|
'');
|
|
|
|
|
|
|
|
$editor = hsprintf(
|
2012-02-24 13:00:48 -08:00
|
|
|
'<div class="maniphest-batch-editor">'.
|
2013-03-12 23:30:03 -07:00
|
|
|
'<div class="batch-editor-header">%s</div>'.
|
2012-02-24 13:00:48 -08:00
|
|
|
'<table class="maniphest-batch-editor-layout">'.
|
|
|
|
'<tr>'.
|
2013-02-13 14:50:15 -08:00
|
|
|
'<td>%s%s</td>'.
|
|
|
|
'<td>%s</td>'.
|
2013-03-12 23:30:03 -07:00
|
|
|
'<td id="batch-select-status-cell">%s</td>'.
|
2013-03-23 14:38:01 -07:00
|
|
|
'<td class="batch-select-submit-cell">%s%s</td>'.
|
2012-02-24 13:00:48 -08:00
|
|
|
'</tr>'.
|
|
|
|
'</table>'.
|
2013-03-23 14:38:01 -07:00
|
|
|
'</div>',
|
2013-03-12 23:30:03 -07:00
|
|
|
pht('Batch Task Editor'),
|
|
|
|
$select_all,
|
|
|
|
$select_none,
|
2013-02-13 14:50:15 -08:00
|
|
|
$export,
|
2013-03-23 14:38:01 -07:00
|
|
|
'',
|
|
|
|
$submit,
|
|
|
|
$hidden);
|
|
|
|
|
|
|
|
$editor = phabricator_form(
|
|
|
|
$user,
|
|
|
|
array(
|
|
|
|
'method' => 'POST',
|
|
|
|
'action' => '/maniphest/batch/',
|
|
|
|
'id' => 'batch-select-form',
|
|
|
|
),
|
|
|
|
$editor);
|
|
|
|
|
|
|
|
return $editor;
|
2012-02-24 13:00:48 -08:00
|
|
|
}
|
|
|
|
|
2012-04-10 09:46:04 -07:00
|
|
|
private function buildQueryFromRequest() {
|
2012-07-31 22:52:46 -07:00
|
|
|
$request = $this->getRequest();
|
|
|
|
$user = $request->getUser();
|
2012-04-10 09:46:04 -07:00
|
|
|
|
|
|
|
$status = $this->getStatusValueFromRequest();
|
|
|
|
$group = $this->getGroupValueFromRequest();
|
|
|
|
$order = $this->getOrderValueFromRequest();
|
|
|
|
|
|
|
|
$user_phids = $request->getStrList(
|
|
|
|
'users',
|
|
|
|
array($user->getPHID()));
|
|
|
|
|
|
|
|
if ($this->view == 'projecttriage' || $this->view == 'projectall') {
|
2012-10-04 15:30:51 -07:00
|
|
|
$projects = id(new PhabricatorProjectQuery())
|
|
|
|
->setViewer($user)
|
|
|
|
->withMemberPHIDs($user_phids)
|
|
|
|
->execute();
|
|
|
|
$any_project_phids = mpull($projects, 'getPHID');
|
2013-04-04 10:29:40 -07:00
|
|
|
$any_user_project_phids = array();
|
2012-04-10 09:46:04 -07:00
|
|
|
} else {
|
2012-10-04 15:30:51 -07:00
|
|
|
$any_project_phids = $request->getStrList('aprojects');
|
2013-04-04 10:29:40 -07:00
|
|
|
$any_user_project_phids = $request->getStrList('useraprojects');
|
2012-04-10 09:46:04 -07:00
|
|
|
}
|
|
|
|
|
2012-10-04 15:30:51 -07:00
|
|
|
$project_phids = $request->getStrList('projects');
|
2012-04-10 09:46:04 -07:00
|
|
|
$exclude_project_phids = $request->getStrList('xprojects');
|
|
|
|
$task_ids = $request->getStrList('tasks');
|
2012-06-15 17:19:56 -07:00
|
|
|
|
2012-06-16 06:27:21 -07:00
|
|
|
if ($task_ids) {
|
|
|
|
// We only need the integer portion of each task ID, so get rid of any
|
|
|
|
// non-numeric elements
|
|
|
|
$numeric_task_ids = array();
|
|
|
|
|
|
|
|
foreach ($task_ids as $task_id) {
|
2013-03-04 13:35:41 -08:00
|
|
|
$task_id = preg_replace('/\D+/', '', $task_id);
|
2012-06-16 06:27:21 -07:00
|
|
|
if (!empty($task_id)) {
|
|
|
|
$numeric_task_ids[] = $task_id;
|
|
|
|
}
|
2012-06-15 17:19:56 -07:00
|
|
|
}
|
|
|
|
|
2012-06-16 06:27:21 -07:00
|
|
|
if (empty($numeric_task_ids)) {
|
|
|
|
$numeric_task_ids = array(null);
|
|
|
|
}
|
2012-06-15 17:19:56 -07:00
|
|
|
|
2012-06-16 06:27:21 -07:00
|
|
|
$task_ids = $numeric_task_ids;
|
|
|
|
}
|
2012-06-15 17:19:56 -07:00
|
|
|
|
2012-07-31 22:52:46 -07:00
|
|
|
$owner_phids = $request->getStrList('owners');
|
|
|
|
$author_phids = $request->getStrList('authors');
|
|
|
|
|
|
|
|
$search_string = $request->getStr('search');
|
2012-04-10 09:46:04 -07:00
|
|
|
|
2012-07-31 22:52:46 -07:00
|
|
|
$low_priority = $request->getInt('lpriority');
|
|
|
|
$high_priority = $request->getInt('hpriority');
|
2012-06-15 14:09:49 -07:00
|
|
|
|
2012-04-10 09:46:04 -07:00
|
|
|
$page = $request->getInt('offset');
|
|
|
|
$page_size = self::DEFAULT_PAGE_SIZE;
|
|
|
|
|
|
|
|
$query = new PhabricatorSearchQuery();
|
|
|
|
$query->setQuery('<<maniphest>>');
|
|
|
|
$query->setParameters(
|
|
|
|
array(
|
2012-06-15 14:09:49 -07:00
|
|
|
'fullTextSearch' => $search_string,
|
2012-04-10 09:46:04 -07:00
|
|
|
'view' => $this->view,
|
|
|
|
'userPHIDs' => $user_phids,
|
|
|
|
'projectPHIDs' => $project_phids,
|
2012-10-04 15:30:51 -07:00
|
|
|
'anyProjectPHIDs' => $any_project_phids,
|
2013-04-04 10:29:40 -07:00
|
|
|
'anyUserProjectPHIDs' => $any_user_project_phids,
|
2012-04-10 09:46:04 -07:00
|
|
|
'excludeProjectPHIDs' => $exclude_project_phids,
|
|
|
|
'ownerPHIDs' => $owner_phids,
|
|
|
|
'authorPHIDs' => $author_phids,
|
|
|
|
'taskIDs' => $task_ids,
|
2012-07-31 22:52:46 -07:00
|
|
|
'lowPriority' => $low_priority,
|
|
|
|
'highPriority' => $high_priority,
|
2012-04-10 09:46:04 -07:00
|
|
|
'group' => $group,
|
|
|
|
'order' => $order,
|
|
|
|
'offset' => $page,
|
|
|
|
'limit' => $page_size,
|
|
|
|
'status' => $status,
|
|
|
|
));
|
|
|
|
|
|
|
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
|
|
|
$query->save();
|
|
|
|
unset($unguarded);
|
|
|
|
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -( Toggle Button Controls )---------------------------------------------
|
|
|
|
|
|
|
|
These are a giant mess since we have several different values: the request
|
|
|
|
key (GET param used in requests), the request value (short names used in
|
|
|
|
requests to keep URIs readable), and the query value (complex value stored in
|
|
|
|
the query).
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private function getStatusValueFromRequest() {
|
|
|
|
$map = $this->getStatusMap();
|
|
|
|
$val = $this->getRequest()->getStr($this->getStatusRequestKey());
|
|
|
|
return idx($map, $val, head($map));
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getGroupValueFromRequest() {
|
|
|
|
$map = $this->getGroupMap();
|
|
|
|
$val = $this->getRequest()->getStr($this->getGroupRequestKey());
|
|
|
|
return idx($map, $val, head($map));
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getOrderValueFromRequest() {
|
|
|
|
$map = $this->getOrderMap();
|
|
|
|
$val = $this->getRequest()->getStr($this->getOrderRequestKey());
|
|
|
|
return idx($map, $val, head($map));
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getStatusRequestKey() {
|
|
|
|
return 's';
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getGroupRequestKey() {
|
|
|
|
return 'g';
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getOrderRequestKey() {
|
|
|
|
return 'o';
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getStatusRequestValue($value) {
|
|
|
|
return array_search($value, $this->getStatusMap());
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getGroupRequestValue($value) {
|
|
|
|
return array_search($value, $this->getGroupMap());
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getOrderRequestValue($value) {
|
|
|
|
return array_search($value, $this->getOrderMap());
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getStatusMap() {
|
|
|
|
return array(
|
|
|
|
'o' => array(
|
|
|
|
'open' => true,
|
|
|
|
),
|
|
|
|
'c' => array(
|
|
|
|
'closed' => true,
|
|
|
|
),
|
|
|
|
'oc' => array(
|
|
|
|
'open' => true,
|
|
|
|
'closed' => true,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getGroupMap() {
|
|
|
|
return array(
|
|
|
|
'p' => 'priority',
|
|
|
|
'o' => 'owner',
|
|
|
|
's' => 'status',
|
|
|
|
'j' => 'project',
|
|
|
|
'n' => 'none',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getOrderMap() {
|
|
|
|
return array(
|
|
|
|
'p' => 'priority',
|
|
|
|
'u' => 'updated',
|
|
|
|
'c' => 'created',
|
2012-08-02 14:21:13 -07:00
|
|
|
't' => 'title',
|
2012-04-10 09:46:04 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getStatusButtonMap() {
|
|
|
|
return array(
|
2013-03-12 23:30:03 -07:00
|
|
|
'o' => pht('Open'),
|
|
|
|
'c' => pht('Closed'),
|
|
|
|
'oc' => pht('All'),
|
2012-04-10 09:46:04 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getGroupButtonMap() {
|
|
|
|
return array(
|
2013-03-12 23:30:03 -07:00
|
|
|
'p' => pht('Priority'),
|
|
|
|
'o' => pht('Owner'),
|
|
|
|
's' => pht('Status'),
|
|
|
|
'j' => pht('Project'),
|
|
|
|
'n' => pht('None'),
|
2012-04-10 09:46:04 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getOrderButtonMap() {
|
|
|
|
return array(
|
2013-03-12 23:30:03 -07:00
|
|
|
'p' => pht('Priority'),
|
|
|
|
'u' => pht('Updated'),
|
|
|
|
'c' => pht('Created'),
|
|
|
|
't' => pht('Title'),
|
2012-04-10 09:46:04 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function renderStatusControl($value) {
|
|
|
|
$request = $this->getRequest();
|
|
|
|
return id(new AphrontFormToggleButtonsControl())
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Status'))
|
2012-04-10 09:46:04 -07:00
|
|
|
->setValue($this->getStatusRequestValue($value))
|
|
|
|
->setBaseURI($request->getRequestURI(), $this->getStatusRequestKey())
|
|
|
|
->setButtons($this->getStatusButtonMap());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function renderOrderControl($value) {
|
|
|
|
$request = $this->getRequest();
|
|
|
|
return id(new AphrontFormToggleButtonsControl())
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Order'))
|
2012-04-10 09:46:04 -07:00
|
|
|
->setValue($this->getOrderRequestValue($value))
|
|
|
|
->setBaseURI($request->getRequestURI(), $this->getOrderRequestKey())
|
|
|
|
->setButtons($this->getOrderButtonMap());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function renderGroupControl($value) {
|
|
|
|
$request = $this->getRequest();
|
|
|
|
return id(new AphrontFormToggleButtonsControl())
|
2013-03-12 23:30:03 -07:00
|
|
|
->setLabel(pht('Group'))
|
2012-04-10 09:46:04 -07:00
|
|
|
->setValue($this->getGroupRequestValue($value))
|
|
|
|
->setBaseURI($request->getRequestURI(), $this->getGroupRequestKey())
|
|
|
|
->setButtons($this->getGroupButtonMap());
|
|
|
|
}
|
|
|
|
|
2011-02-08 10:53:59 -08:00
|
|
|
}
|