2011-02-08 19:53:59 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
2012-01-04 06:57:45 +01:00
|
|
|
* Copyright 2012 Facebook, Inc.
|
2011-02-08 19:53:59 +01:00
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2011-07-04 22:04:22 +02:00
|
|
|
/**
|
|
|
|
* @group maniphest
|
|
|
|
*/
|
2012-03-10 00:46:25 +01:00
|
|
|
final class ManiphestTaskListController extends ManiphestController {
|
2011-02-08 19:53:59 +01: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-27 03:50:17 +02:00
|
|
|
const DEFAULT_PAGE_SIZE = 1000;
|
|
|
|
|
2011-02-08 19:53:59 +01:00
|
|
|
private $view;
|
|
|
|
|
|
|
|
public function willProcessRequest(array $data) {
|
|
|
|
$this->view = idx($data, 'view');
|
|
|
|
}
|
|
|
|
|
2012-02-29 06:08:02 +01:00
|
|
|
private function getArrToStrList($key) {
|
|
|
|
$arr = $this->getRequest()->getArr($key);
|
|
|
|
$arr = implode(',', $arr);
|
|
|
|
return nonempty($arr, null);
|
|
|
|
}
|
|
|
|
|
2011-02-08 19:53:59 +01:00
|
|
|
public function processRequest() {
|
|
|
|
|
2011-04-11 11:03:30 +02:00
|
|
|
$request = $this->getRequest();
|
|
|
|
$user = $request->getUser();
|
|
|
|
|
|
|
|
if ($request->isFormPost()) {
|
2011-06-30 01:16:33 +02:00
|
|
|
// Redirect to GET so URIs can be copy/pasted.
|
|
|
|
|
2011-12-02 16:30:20 +01:00
|
|
|
$task_ids = $request->getStr('set_tasks');
|
|
|
|
$task_ids = nonempty($task_ids, null);
|
2011-04-11 11:03:30 +02:00
|
|
|
|
2012-06-15 23:09:49 +02:00
|
|
|
$search_text = $request->getStr('set_search');
|
|
|
|
$search_text = nonempty($search_text, null);
|
|
|
|
|
2012-08-01 07:52:46 +02:00
|
|
|
$min_priority = $request->getInt('set_lpriority');
|
|
|
|
$min_priority = nonempty($min_priority, null);
|
|
|
|
|
|
|
|
$max_priority = $request->getInt('set_hpriority');
|
|
|
|
$max_priority = nonempty($max_priority, null);
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
$uri = $request->getRequestURI()
|
2012-02-29 06:08:02 +01:00
|
|
|
->alter('users', $this->getArrToStrList('set_users'))
|
|
|
|
->alter('projects', $this->getArrToStrList('set_projects'))
|
|
|
|
->alter('xprojects', $this->getArrToStrList('set_xprojects'))
|
|
|
|
->alter('owners', $this->getArrToStrList('set_owners'))
|
|
|
|
->alter('authors', $this->getArrToStrList('set_authors'))
|
2012-08-01 07:52:46 +02:00
|
|
|
->alter('lpriority', $min_priority)
|
|
|
|
->alter('hpriority', $max_priority)
|
2012-06-15 23:09:49 +02:00
|
|
|
->alter('tasks', $task_ids)
|
|
|
|
->alter('search', $search_text);
|
2011-06-30 01:16:33 +02:00
|
|
|
|
|
|
|
return id(new AphrontRedirectResponse())->setURI($uri);
|
|
|
|
}
|
2011-04-11 11:03:30 +02:00
|
|
|
|
2012-03-01 23:19:11 +01:00
|
|
|
$nav = $this->buildBaseSideNav();
|
2011-12-20 23:36:54 +01:00
|
|
|
|
2011-04-11 11:03:30 +02:00
|
|
|
$has_filter = array(
|
|
|
|
'action' => true,
|
|
|
|
'created' => true,
|
2011-07-07 19:24:49 +02:00
|
|
|
'subscribed' => true,
|
2011-04-11 11:03:30 +02:00
|
|
|
'triage' => true,
|
2012-03-15 02:01:14 +01:00
|
|
|
'projecttriage' => true,
|
|
|
|
'projectall' => true,
|
2011-04-11 11:03:30 +02:00
|
|
|
);
|
2011-02-08 19:53:59 +01:00
|
|
|
|
2012-04-10 18:46:04 +02:00
|
|
|
$query = null;
|
|
|
|
$key = $request->getStr('key');
|
|
|
|
if (!$key && !$this->view) {
|
|
|
|
if ($this->getDefaultQuery()) {
|
|
|
|
$key = $this->getDefaultQuery()->getQueryKey();
|
|
|
|
}
|
|
|
|
}
|
2011-02-11 19:28:37 +01:00
|
|
|
|
2012-04-10 18:46:04 +02:00
|
|
|
if ($key) {
|
|
|
|
$query = id(new PhabricatorSearchQuery())->loadOneWhere(
|
|
|
|
'queryKey = %s',
|
|
|
|
$key);
|
2012-03-15 02:01:14 +01:00
|
|
|
}
|
2011-12-02 16:30:20 +01:00
|
|
|
|
2012-04-10 18:46:04 +02: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-27 03:50:17 +02:00
|
|
|
|
2012-04-10 18:46:04 +02: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 19:53:59 +01:00
|
|
|
|
2012-04-10 18:46:04 +02:00
|
|
|
// Execute the query.
|
2012-02-29 06:07:12 +01:00
|
|
|
|
|
|
|
list($tasks, $handles, $total_count) = self::loadTasks($query);
|
|
|
|
|
2012-04-10 18:46:04 +02:00
|
|
|
// Extract information we need to render the filters from the query.
|
|
|
|
|
2012-06-15 23:09:49 +02:00
|
|
|
$search_text = $query->getParameter('fullTextSearch');
|
|
|
|
|
2012-04-10 18:46:04 +02: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());
|
|
|
|
$exclude_project_phids = $query->getParameter(
|
|
|
|
'excludeProjectPHIDs',
|
|
|
|
array());
|
2012-08-01 07:52:46 +02:00
|
|
|
$low_priority = $query->getParameter('lowPriority');
|
|
|
|
$high_priority = $query->getParameter('highPriority');
|
|
|
|
|
2012-04-10 18:46:04 +02: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-04 00:50:06 +02:00
|
|
|
$form = id(new AphrontFormView())
|
2011-06-30 01:16:33 +02:00
|
|
|
->setUser($user)
|
2012-04-10 18:46:04 +02: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 11:03:30 +02:00
|
|
|
|
|
|
|
if (isset($has_filter[$this->view])) {
|
2011-06-30 01:16:33 +02:00
|
|
|
$tokens = array();
|
|
|
|
foreach ($user_phids as $phid) {
|
|
|
|
$tokens[$phid] = $handles[$phid]->getFullName();
|
|
|
|
}
|
2011-04-11 11:03:30 +02:00
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTokenizerControl())
|
2011-05-28 23:13:12 +02:00
|
|
|
->setDatasource('/typeahead/common/searchowner/')
|
2011-06-30 01:16:33 +02:00
|
|
|
->setName('set_users')
|
|
|
|
->setLabel('Users')
|
|
|
|
->setValue($tokens));
|
|
|
|
}
|
|
|
|
|
2011-12-02 16:30:20 +01:00
|
|
|
if ($this->view == 'custom') {
|
2012-06-15 23:09:49 +02:00
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTextControl())
|
|
|
|
->setName('set_search')
|
|
|
|
->setLabel('Search')
|
|
|
|
->setValue($search_text)
|
|
|
|
);
|
2011-12-02 16:30:20 +01:00
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTextControl())
|
|
|
|
->setName('set_tasks')
|
|
|
|
->setLabel('Task IDs')
|
|
|
|
->setValue(join(',', $task_ids))
|
|
|
|
);
|
2012-02-29 06:08:02 +01: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')
|
|
|
|
->setLabel('Owners')
|
|
|
|
->setValue($tokens));
|
|
|
|
|
|
|
|
$tokens = array();
|
|
|
|
foreach ($author_phids as $phid) {
|
|
|
|
$tokens[$phid] = $handles[$phid]->getFullName();
|
|
|
|
}
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTokenizerControl())
|
|
|
|
->setDatasource('/typeahead/common/users/')
|
|
|
|
->setName('set_authors')
|
|
|
|
->setLabel('Authors')
|
|
|
|
->setValue($tokens));
|
2011-12-02 16:30:20 +01:00
|
|
|
}
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
$tokens = array();
|
|
|
|
foreach ($project_phids as $phid) {
|
|
|
|
$tokens[$phid] = $handles[$phid]->getFullName();
|
2011-04-11 11:03:30 +02:00
|
|
|
}
|
2012-03-15 02:01:14 +01:00
|
|
|
if ($this->view != 'projectall' && $this->view != 'projecttriage') {
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormTokenizerControl())
|
|
|
|
->setDatasource('/typeahead/common/searchproject/')
|
|
|
|
->setName('set_projects')
|
|
|
|
->setLabel('Projects')
|
|
|
|
->setValue($tokens));
|
|
|
|
}
|
2011-04-11 11:03:30 +02:00
|
|
|
|
2012-02-29 06:08:02 +01:00
|
|
|
if ($this->view == 'custom') {
|
|
|
|
$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')
|
|
|
|
->setLabel('Exclude Projects')
|
|
|
|
->setValue($tokens));
|
2012-08-01 07:52:46 +02:00
|
|
|
|
|
|
|
$priority = ManiphestTaskPriority::getLowestPriority();
|
|
|
|
if ($low_priority) {
|
|
|
|
$priority = $low_priority;
|
|
|
|
}
|
|
|
|
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormSelectControl())
|
2012-08-01 07:54:45 +02:00
|
|
|
->setLabel('Min Priority')
|
2012-08-01 07:52:46 +02:00
|
|
|
->setName('set_lpriority')
|
|
|
|
->setValue($priority)
|
|
|
|
->setOptions(array_reverse(
|
|
|
|
ManiphestTaskPriority::getTaskPriorityMap(), true)));
|
|
|
|
|
|
|
|
$priority = ManiphestTaskPriority::getHighestPriority();
|
|
|
|
if ($high_priority) {
|
|
|
|
$priority = $high_priority;
|
|
|
|
}
|
|
|
|
|
|
|
|
$form->appendChild(
|
|
|
|
id(new AphrontFormSelectControl())
|
2012-08-01 07:54:45 +02:00
|
|
|
->setLabel('Max Priority')
|
2012-08-01 07:52:46 +02:00
|
|
|
->setName('set_hpriority')
|
|
|
|
->setValue($priority)
|
|
|
|
->setOptions(ManiphestTaskPriority::getTaskPriorityMap()));
|
|
|
|
|
2012-02-29 06:08:02 +01:00
|
|
|
}
|
|
|
|
|
2011-04-11 11:03:30 +02:00
|
|
|
$form
|
2012-04-10 18:46:04 +02:00
|
|
|
->appendChild($this->renderStatusControl($q_status))
|
|
|
|
->appendChild($this->renderGroupControl($q_group))
|
|
|
|
->appendChild($this->renderOrderControl($q_order));
|
|
|
|
|
|
|
|
$submit = id(new AphrontFormSubmitControl())
|
|
|
|
->setValue('Filter Tasks');
|
|
|
|
|
|
|
|
// 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(),
|
|
|
|
'Save Custom Query...');
|
|
|
|
}
|
2011-04-04 00:50:06 +02:00
|
|
|
|
2012-04-10 18:46:04 +02:00
|
|
|
$form->appendChild($submit);
|
2011-06-30 01:16:33 +02:00
|
|
|
|
2011-08-01 22:51:27 +02: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-04 00:50:06 +02:00
|
|
|
$filter = new AphrontListFilterView();
|
|
|
|
$filter->addButton(
|
|
|
|
phutil_render_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
2011-08-01 22:51:27 +02:00
|
|
|
'href' => (string)$create_uri,
|
2011-04-04 00:50:06 +02:00
|
|
|
'class' => 'green button',
|
|
|
|
),
|
|
|
|
'Create New Task'));
|
2012-04-10 18:46:04 +02:00
|
|
|
|
|
|
|
if (empty($key)) {
|
|
|
|
$filter->appendChild($form);
|
|
|
|
}
|
2011-04-04 00:50:06 +02:00
|
|
|
|
|
|
|
$nav->appendChild($filter);
|
2011-02-11 19:28:37 +01:00
|
|
|
|
|
|
|
$have_tasks = false;
|
|
|
|
foreach ($tasks as $group => $list) {
|
|
|
|
if (count($list)) {
|
|
|
|
$have_tasks = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-04 00:50:06 +02:00
|
|
|
require_celerity_resource('maniphest-task-summary-css');
|
|
|
|
|
2012-03-05 22:51:35 +01:00
|
|
|
$list_container = new AphrontNullView();
|
|
|
|
$list_container->appendChild('<div class="maniphest-list-container">');
|
|
|
|
|
2011-02-11 19:28:37 +01:00
|
|
|
if (!$have_tasks) {
|
2012-03-05 22:51:35 +01:00
|
|
|
$list_container->appendChild(
|
2011-02-11 19:28:37 +01:00
|
|
|
'<h1 class="maniphest-task-group-header">'.
|
|
|
|
'No matching tasks.'.
|
|
|
|
'</h1>');
|
|
|
|
} 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-27 03:50:17 +02:00
|
|
|
$pager = new AphrontPagerView();
|
2012-04-10 18:46:04 +02: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-27 03:50:17 +02: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;
|
|
|
|
|
|
|
|
$cur = number_format($cur);
|
|
|
|
$max = number_format($max);
|
|
|
|
$tot = number_format($tot);
|
|
|
|
|
2012-03-05 22:51:35 +01:00
|
|
|
$list_container->appendChild(
|
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-27 03:50:17 +02:00
|
|
|
'<div class="maniphest-total-result-count">'.
|
|
|
|
"Displaying tasks {$cur} - {$max} of {$tot}.".
|
|
|
|
'</div>');
|
|
|
|
|
2012-02-24 22:00:48 +01:00
|
|
|
$selector = new AphrontNullView();
|
|
|
|
|
2012-04-02 21:12:04 +02:00
|
|
|
$group = $query->getParameter('group');
|
|
|
|
$order = $query->getParameter('order');
|
|
|
|
$is_draggable =
|
|
|
|
($group == 'priority') ||
|
|
|
|
($group == 'none' && $order == 'priority');
|
|
|
|
|
|
|
|
$lists = new AphrontNullView();
|
2012-04-02 21:14:26 +02:00
|
|
|
$lists->appendChild('<div class="maniphest-group-container">');
|
2011-02-11 19:28:37 +01:00
|
|
|
foreach ($tasks as $group => $list) {
|
|
|
|
$task_list = new ManiphestTaskListView();
|
2012-02-24 22:00:48 +01:00
|
|
|
$task_list->setShowBatchControls(true);
|
2012-04-02 21:12:04 +02: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 18:22:52 +02:00
|
|
|
$task_list->setUser($user);
|
2011-02-11 19:28:37 +01:00
|
|
|
$task_list->setTasks($list);
|
|
|
|
$task_list->setHandles($handles);
|
|
|
|
|
2011-06-14 05:03:44 +02:00
|
|
|
$count = number_format(count($list));
|
2012-04-02 21:12:04 +02:00
|
|
|
|
|
|
|
$lists->appendChild(
|
|
|
|
javelin_render_tag(
|
|
|
|
'h1',
|
|
|
|
array(
|
|
|
|
'class' => 'maniphest-task-group-header',
|
|
|
|
'sigil' => 'task-group',
|
|
|
|
'meta' => array(
|
|
|
|
'priority' => head($list)->getPriority(),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
phutil_escape_html($group).' ('.$count.')'));
|
|
|
|
|
|
|
|
$lists->appendChild($task_list);
|
2011-02-11 19:28:37 +01:00
|
|
|
}
|
2012-04-02 21:12:04 +02:00
|
|
|
$lists->appendChild('</div>');
|
|
|
|
$selector->appendChild($lists);
|
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-27 03:50:17 +02:00
|
|
|
|
2012-02-24 22:00:48 +01:00
|
|
|
|
2012-02-29 06:07:12 +01:00
|
|
|
$selector->appendChild($this->renderBatchEditor($query));
|
2012-02-24 22:00:48 +01:00
|
|
|
|
2012-04-02 21:12:04 +02:00
|
|
|
$form_id = celerity_generate_unique_node_id();
|
2012-02-24 22:00:48 +01:00
|
|
|
$selector = phabricator_render_form(
|
|
|
|
$user,
|
|
|
|
array(
|
|
|
|
'method' => 'POST',
|
|
|
|
'action' => '/maniphest/batch/',
|
2012-04-02 21:12:04 +02:00
|
|
|
'id' => $form_id,
|
2012-02-24 22:00:48 +01:00
|
|
|
),
|
|
|
|
$selector->render());
|
|
|
|
|
2012-03-05 22:51:35 +01:00
|
|
|
$list_container->appendChild($selector);
|
|
|
|
$list_container->appendChild($pager);
|
2012-04-02 21:12:04 +02:00
|
|
|
|
|
|
|
Javelin::initBehavior(
|
|
|
|
'maniphest-subpriority-editor',
|
|
|
|
array(
|
|
|
|
'root' => $form_id,
|
|
|
|
'uri' => '/maniphest/subpriority/',
|
|
|
|
));
|
2011-02-11 19:28:37 +01:00
|
|
|
}
|
2011-02-08 19:53:59 +01:00
|
|
|
|
2012-03-05 22:51:35 +01:00
|
|
|
$list_container->appendChild('</div>');
|
|
|
|
$nav->appendChild($list_container);
|
|
|
|
|
2011-02-08 19:53:59 +01:00
|
|
|
return $this->buildStandardPageResponse(
|
|
|
|
$nav,
|
|
|
|
array(
|
|
|
|
'title' => 'Task List',
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2012-02-29 06:07:12 +01:00
|
|
|
public static function loadTasks(PhabricatorSearchQuery $search_query) {
|
2012-03-30 19:55:18 +02:00
|
|
|
$any_project = false;
|
2012-06-15 23:09:49 +02:00
|
|
|
$search_text = $search_query->getParameter('fullTextSearch');
|
2012-02-29 06:07:12 +01:00
|
|
|
$user_phids = $search_query->getParameter('userPHIDs', array());
|
|
|
|
$project_phids = $search_query->getParameter('projectPHIDs', array());
|
|
|
|
$task_ids = $search_query->getParameter('taskIDs', array());
|
2012-02-29 06:08:02 +01: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 19:53:59 +01:00
|
|
|
|
2012-08-01 07:52:46 +02:00
|
|
|
$low_priority = $search_query->getParameter('lowPriority');
|
|
|
|
$low_priority = nonempty($low_priority,
|
|
|
|
ManiphestTaskPriority::getLowestPriority());
|
|
|
|
$high_priority = $search_query->getParameter('highPriority');
|
|
|
|
$high_priority = nonempty($high_priority,
|
|
|
|
ManiphestTaskPriority::getHighestPriority());
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
$query = new ManiphestTaskQuery();
|
|
|
|
$query->withProjects($project_phids);
|
2011-12-02 16:30:20 +01:00
|
|
|
$query->withTaskIDs($task_ids);
|
2011-02-11 19:28:37 +01:00
|
|
|
|
2012-02-29 06:08:02 +01:00
|
|
|
if ($xproject_phids) {
|
|
|
|
$query->withoutProjects($xproject_phids);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($owner_phids) {
|
|
|
|
$query->withOwners($owner_phids);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($author_phids) {
|
|
|
|
$query->withAuthors($author_phids);
|
|
|
|
}
|
|
|
|
|
2012-02-29 06:07:12 +01:00
|
|
|
$status = $search_query->getParameter('status', 'all');
|
2011-02-11 19:28:37 +01:00
|
|
|
if (!empty($status['open']) && !empty($status['closed'])) {
|
2011-06-30 01:16:33 +02:00
|
|
|
$query->withStatus(ManiphestTaskQuery::STATUS_ANY);
|
2011-02-11 19:28:37 +01:00
|
|
|
} else if (!empty($status['open'])) {
|
2011-06-30 01:16:33 +02:00
|
|
|
$query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
|
2011-02-11 19:28:37 +01:00
|
|
|
} else {
|
2011-06-30 01:16:33 +02:00
|
|
|
$query->withStatus(ManiphestTaskQuery::STATUS_CLOSED);
|
2011-02-11 19:28:37 +01:00
|
|
|
}
|
|
|
|
|
2012-02-29 06:07:12 +01:00
|
|
|
switch ($search_query->getParameter('view')) {
|
2011-02-08 19:53:59 +01:00
|
|
|
case 'action':
|
2011-06-30 01:16:33 +02:00
|
|
|
$query->withOwners($user_phids);
|
2011-02-11 19:28:37 +01:00
|
|
|
break;
|
2011-02-08 19:53:59 +01:00
|
|
|
case 'created':
|
2011-06-30 01:16:33 +02:00
|
|
|
$query->withAuthors($user_phids);
|
2011-02-11 19:28:37 +01:00
|
|
|
break;
|
2011-07-07 19:24:49 +02:00
|
|
|
case 'subscribed':
|
|
|
|
$query->withSubscribers($user_phids);
|
|
|
|
break;
|
2011-02-08 19:53:59 +01:00
|
|
|
case 'triage':
|
2011-06-30 01:16:33 +02:00
|
|
|
$query->withOwners($user_phids);
|
|
|
|
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
|
2011-02-11 19:28:37 +01:00
|
|
|
break;
|
2011-02-10 01:29:46 +01:00
|
|
|
case 'alltriage':
|
2011-06-30 01:16:33 +02:00
|
|
|
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
|
2011-02-11 19:28:37 +01:00
|
|
|
break;
|
|
|
|
case 'all':
|
|
|
|
break;
|
2012-03-15 02:01:14 +01:00
|
|
|
case 'projecttriage':
|
|
|
|
$query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
|
2012-03-30 19:55:18 +02:00
|
|
|
$any_project = true;
|
2012-03-15 02:01:14 +01:00
|
|
|
break;
|
|
|
|
case 'projectall':
|
2012-03-30 19:55:18 +02:00
|
|
|
$any_project = true;
|
2012-03-15 02:01:14 +01:00
|
|
|
break;
|
2012-08-01 07:52:46 +02:00
|
|
|
case 'custom':
|
|
|
|
$query->withPrioritiesBetween($low_priority, $high_priority);
|
|
|
|
break;
|
2011-02-11 19:28:37 +01:00
|
|
|
}
|
|
|
|
|
2012-03-30 19:55:18 +02:00
|
|
|
$query->withAnyProject($any_project);
|
|
|
|
|
2012-06-15 23:09:49 +02:00
|
|
|
$query->withFullTextSearch($search_text);
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
$order_map = array(
|
|
|
|
'priority' => ManiphestTaskQuery::ORDER_PRIORITY,
|
|
|
|
'created' => ManiphestTaskQuery::ORDER_CREATED,
|
2012-08-02 23:21:13 +02:00
|
|
|
'title' => ManiphestTaskQuery::ORDER_TITLE,
|
2011-06-30 01:16:33 +02:00
|
|
|
);
|
|
|
|
$query->setOrderBy(
|
|
|
|
idx(
|
|
|
|
$order_map,
|
2012-02-29 06:07:12 +01:00
|
|
|
$search_query->getParameter('order'),
|
2011-06-30 01:16:33 +02: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-20 03:47:34 +01:00
|
|
|
'project' => ManiphestTaskQuery::GROUP_PROJECT,
|
2011-06-30 01:16:33 +02:00
|
|
|
);
|
|
|
|
$query->setGroupBy(
|
|
|
|
idx(
|
|
|
|
$group_map,
|
2012-02-29 06:07:12 +01:00
|
|
|
$search_query->getParameter('group'),
|
2011-06-30 01:16:33 +02: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-27 03:50:17 +02:00
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
$query->setCalculateRows(true);
|
2012-02-29 06:07:12 +01: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-27 03:50:17 +02:00
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
$data = $query->execute();
|
|
|
|
$total_row_count = $query->getRowCount();
|
2011-02-11 19:28:37 +01: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-20 03:47:34 +01: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 19:28:37 +01:00
|
|
|
$handle_phids = mpull($data, 'getOwnerPHID');
|
2012-02-29 06:08:02 +01: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-20 03:47:34 +01:00
|
|
|
$author_phids,
|
2012-04-02 19:27:31 +02:00
|
|
|
$project_group_phids,
|
|
|
|
array_mergev(mpull($data, 'getProjectPHIDs')));
|
2011-02-11 19:28:37 +01:00
|
|
|
$handles = id(new PhabricatorObjectHandleData($handle_phids))
|
|
|
|
->loadHandles();
|
|
|
|
|
2012-02-29 06:07:12 +01:00
|
|
|
switch ($search_query->getParameter('group')) {
|
2011-02-11 19:28:37 +01: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-27 03:50:17 +02:00
|
|
|
// If we have invalid priorities, they'll all map to "???". Merge
|
|
|
|
// arrays to prevent them from overwriting each other.
|
|
|
|
|
2011-02-11 19:28:37 +01: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-27 03:50:17 +02:00
|
|
|
$out[ManiphestTaskPriority::getTaskPriorityName($pri)][] = $tasks;
|
|
|
|
}
|
|
|
|
foreach ($out as $pri => $tasks) {
|
|
|
|
$out[$pri] = array_mergev($tasks);
|
2011-02-11 19:28:37 +01: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 21:55:22 +01:00
|
|
|
$data = $out;
|
2011-02-11 19:28:37 +01:00
|
|
|
ksort($data);
|
2012-03-22 21:55:22 +01:00
|
|
|
|
|
|
|
// Move "Unassigned" to the top of the list.
|
|
|
|
if (isset($data['Unassigned'])) {
|
|
|
|
$data = array('Unassigned' => $out['Unassigned']) + $out;
|
|
|
|
}
|
2011-02-11 19:28:37 +01: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-20 03:47:34 +01:00
|
|
|
case 'project':
|
|
|
|
$grouped = array();
|
|
|
|
foreach ($data as $task) {
|
|
|
|
$phids = $task->getProjectPHIDs();
|
2012-03-30 19:55:18 +02:00
|
|
|
if ($project_phids && $any_project !== true) {
|
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-20 03:47:34 +01:00
|
|
|
// If the user is filtering on "Bugs", don't show a "Bugs" group
|
|
|
|
// with every result since that's silly (the query also does this
|
|
|
|
// on the backend).
|
|
|
|
$phids = array_diff($phids, $project_phids);
|
|
|
|
}
|
|
|
|
if ($phids) {
|
|
|
|
foreach ($phids as $phid) {
|
|
|
|
$grouped[$handles[$phid]->getName()][$task->getID()] = $task;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$grouped['No Project'][$task->getID()] = $task;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$data = $grouped;
|
2012-03-22 21:55:22 +01: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-20 03:47:34 +01:00
|
|
|
break;
|
2011-02-11 19:28:37 +01: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-27 03:50:17 +02:00
|
|
|
return array($data, $handles, $total_row_count);
|
2011-02-11 19:28:37 +01:00
|
|
|
}
|
|
|
|
|
2012-02-29 06:07:12 +01:00
|
|
|
private function renderBatchEditor(PhabricatorSearchQuery $search_query) {
|
2012-02-24 22:00:48 +01:00
|
|
|
Javelin::initBehavior(
|
|
|
|
'maniphest-batch-selector',
|
|
|
|
array(
|
|
|
|
'selectAll' => 'batch-select-all',
|
|
|
|
'selectNone' => 'batch-select-none',
|
|
|
|
'submit' => 'batch-select-submit',
|
|
|
|
'status' => 'batch-select-status-cell',
|
|
|
|
));
|
|
|
|
|
|
|
|
$select_all = javelin_render_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => '#',
|
|
|
|
'mustcapture' => true,
|
|
|
|
'class' => 'grey button',
|
|
|
|
'id' => 'batch-select-all',
|
|
|
|
),
|
|
|
|
'Select All');
|
|
|
|
|
|
|
|
$select_none = javelin_render_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => '#',
|
|
|
|
'mustcapture' => true,
|
|
|
|
'class' => 'grey button',
|
|
|
|
'id' => 'batch-select-none',
|
|
|
|
),
|
|
|
|
'Clear Selection');
|
|
|
|
|
|
|
|
$submit = phutil_render_tag(
|
|
|
|
'button',
|
|
|
|
array(
|
|
|
|
'id' => 'batch-select-submit',
|
|
|
|
'disabled' => 'disabled',
|
|
|
|
'class' => 'disabled',
|
|
|
|
),
|
|
|
|
'Batch Edit Selected Tasks »');
|
|
|
|
|
2012-02-29 06:07:12 +01:00
|
|
|
$export = javelin_render_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => '/maniphest/export/'.$search_query->getQueryKey().'/',
|
|
|
|
'class' => 'grey button',
|
|
|
|
),
|
|
|
|
'Export Tasks to Excel...');
|
|
|
|
|
2012-02-24 22:00:48 +01:00
|
|
|
return
|
|
|
|
'<div class="maniphest-batch-editor">'.
|
|
|
|
'<div class="batch-editor-header">Batch Task Editor</div>'.
|
|
|
|
'<table class="maniphest-batch-editor-layout">'.
|
|
|
|
'<tr>'.
|
|
|
|
'<td>'.
|
|
|
|
$select_all.
|
|
|
|
$select_none.
|
|
|
|
'</td>'.
|
2012-02-29 06:07:12 +01:00
|
|
|
'<td>'.
|
|
|
|
$export.
|
|
|
|
'</td>'.
|
2012-02-24 22:00:48 +01:00
|
|
|
'<td id="batch-select-status-cell">'.
|
|
|
|
'0 Selected Tasks'.
|
|
|
|
'</td>'.
|
|
|
|
'<td class="batch-select-submit-cell">'.$submit.'</td>'.
|
|
|
|
'</tr>'.
|
|
|
|
'</table>'.
|
|
|
|
'</table>';
|
|
|
|
}
|
|
|
|
|
2012-04-10 18:46:04 +02:00
|
|
|
private function buildQueryFromRequest() {
|
2012-08-01 07:52:46 +02:00
|
|
|
$request = $this->getRequest();
|
|
|
|
$user = $request->getUser();
|
2012-04-10 18:46:04 +02: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') {
|
|
|
|
$project_query = new PhabricatorProjectQuery();
|
2012-08-07 20:54:24 +02:00
|
|
|
$project_query->withMemberPHIDs($user_phids);
|
2012-04-10 18:46:04 +02:00
|
|
|
$projects = $project_query->execute();
|
|
|
|
$project_phids = mpull($projects, 'getPHID');
|
|
|
|
} else {
|
|
|
|
$project_phids = $request->getStrList('projects');
|
|
|
|
}
|
|
|
|
|
|
|
|
$exclude_project_phids = $request->getStrList('xprojects');
|
|
|
|
$task_ids = $request->getStrList('tasks');
|
2012-06-16 02:19:56 +02:00
|
|
|
|
2012-06-16 15:27:21 +02: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) {
|
|
|
|
$task_id = preg_replace('/[a-zA-Z]+/', '', $task_id);
|
|
|
|
if (!empty($task_id)) {
|
|
|
|
$numeric_task_ids[] = $task_id;
|
|
|
|
}
|
2012-06-16 02:19:56 +02:00
|
|
|
}
|
|
|
|
|
2012-06-16 15:27:21 +02:00
|
|
|
if (empty($numeric_task_ids)) {
|
|
|
|
$numeric_task_ids = array(null);
|
|
|
|
}
|
2012-06-16 02:19:56 +02:00
|
|
|
|
2012-06-16 15:27:21 +02:00
|
|
|
$task_ids = $numeric_task_ids;
|
|
|
|
}
|
2012-06-16 02:19:56 +02:00
|
|
|
|
2012-08-01 07:52:46 +02:00
|
|
|
$owner_phids = $request->getStrList('owners');
|
|
|
|
$author_phids = $request->getStrList('authors');
|
|
|
|
|
|
|
|
$search_string = $request->getStr('search');
|
2012-04-10 18:46:04 +02:00
|
|
|
|
2012-08-01 07:52:46 +02:00
|
|
|
$low_priority = $request->getInt('lpriority');
|
|
|
|
$high_priority = $request->getInt('hpriority');
|
2012-06-15 23:09:49 +02:00
|
|
|
|
2012-04-10 18:46:04 +02:00
|
|
|
$page = $request->getInt('offset');
|
|
|
|
$page_size = self::DEFAULT_PAGE_SIZE;
|
|
|
|
|
|
|
|
$query = new PhabricatorSearchQuery();
|
|
|
|
$query->setQuery('<<maniphest>>');
|
|
|
|
$query->setParameters(
|
|
|
|
array(
|
2012-06-15 23:09:49 +02:00
|
|
|
'fullTextSearch' => $search_string,
|
2012-04-10 18:46:04 +02:00
|
|
|
'view' => $this->view,
|
|
|
|
'userPHIDs' => $user_phids,
|
|
|
|
'projectPHIDs' => $project_phids,
|
|
|
|
'excludeProjectPHIDs' => $exclude_project_phids,
|
|
|
|
'ownerPHIDs' => $owner_phids,
|
|
|
|
'authorPHIDs' => $author_phids,
|
|
|
|
'taskIDs' => $task_ids,
|
2012-08-01 07:52:46 +02:00
|
|
|
'lowPriority' => $low_priority,
|
|
|
|
'highPriority' => $high_priority,
|
2012-04-10 18:46:04 +02: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 23:21:13 +02:00
|
|
|
't' => 'title',
|
2012-04-10 18:46:04 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getStatusButtonMap() {
|
|
|
|
return array(
|
|
|
|
'o' => 'Open',
|
|
|
|
'c' => 'Closed',
|
|
|
|
'oc' => 'All',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getGroupButtonMap() {
|
|
|
|
return array(
|
|
|
|
'p' => 'Priority',
|
|
|
|
'o' => 'Owner',
|
|
|
|
's' => 'Status',
|
|
|
|
'j' => 'Project',
|
|
|
|
'n' => 'None',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getOrderButtonMap() {
|
|
|
|
return array(
|
|
|
|
'p' => 'Priority',
|
|
|
|
'u' => 'Updated',
|
|
|
|
'c' => 'Created',
|
2012-08-02 23:21:13 +02:00
|
|
|
't' => 'Title',
|
2012-04-10 18:46:04 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function renderStatusControl($value) {
|
|
|
|
$request = $this->getRequest();
|
|
|
|
return id(new AphrontFormToggleButtonsControl())
|
|
|
|
->setLabel('Status')
|
|
|
|
->setValue($this->getStatusRequestValue($value))
|
|
|
|
->setBaseURI($request->getRequestURI(), $this->getStatusRequestKey())
|
|
|
|
->setButtons($this->getStatusButtonMap());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function renderOrderControl($value) {
|
|
|
|
$request = $this->getRequest();
|
|
|
|
return id(new AphrontFormToggleButtonsControl())
|
|
|
|
->setLabel('Order')
|
|
|
|
->setValue($this->getOrderRequestValue($value))
|
|
|
|
->setBaseURI($request->getRequestURI(), $this->getOrderRequestKey())
|
|
|
|
->setButtons($this->getOrderButtonMap());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function renderGroupControl($value) {
|
|
|
|
$request = $this->getRequest();
|
|
|
|
return id(new AphrontFormToggleButtonsControl())
|
|
|
|
->setLabel('Group')
|
|
|
|
->setValue($this->getGroupRequestValue($value))
|
|
|
|
->setBaseURI($request->getRequestURI(), $this->getGroupRequestKey())
|
|
|
|
->setButtons($this->getGroupButtonMap());
|
|
|
|
}
|
|
|
|
|
2011-02-08 19:53:59 +01:00
|
|
|
}
|