mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-22 20:51:10 +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
This commit is contained in:
parent
7cfe006c7f
commit
afed8bb929
3 changed files with 117 additions and 4 deletions
|
@ -378,6 +378,7 @@ final class ManiphestTaskListController extends ManiphestController {
|
||||||
'priority' => ManiphestTaskQuery::GROUP_PRIORITY,
|
'priority' => ManiphestTaskQuery::GROUP_PRIORITY,
|
||||||
'owner' => ManiphestTaskQuery::GROUP_OWNER,
|
'owner' => ManiphestTaskQuery::GROUP_OWNER,
|
||||||
'status' => ManiphestTaskQuery::GROUP_STATUS,
|
'status' => ManiphestTaskQuery::GROUP_STATUS,
|
||||||
|
'project' => ManiphestTaskQuery::GROUP_PROJECT,
|
||||||
);
|
);
|
||||||
$query->setGroupBy(
|
$query->setGroupBy(
|
||||||
idx(
|
idx(
|
||||||
|
@ -392,6 +393,15 @@ final class ManiphestTaskListController extends ManiphestController {
|
||||||
$data = $query->execute();
|
$data = $query->execute();
|
||||||
$total_row_count = $query->getRowCount();
|
$total_row_count = $query->getRowCount();
|
||||||
|
|
||||||
|
$project_group_phids = array();
|
||||||
|
if ($search_query->getParameter('group') == 'project') {
|
||||||
|
foreach ($data as $task) {
|
||||||
|
foreach ($task->getProjectPHIDs() as $phid) {
|
||||||
|
$project_group_phids[] = $phid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$handle_phids = mpull($data, 'getOwnerPHID');
|
$handle_phids = mpull($data, 'getOwnerPHID');
|
||||||
$handle_phids = array_merge(
|
$handle_phids = array_merge(
|
||||||
$handle_phids,
|
$handle_phids,
|
||||||
|
@ -399,14 +409,14 @@ final class ManiphestTaskListController extends ManiphestController {
|
||||||
$user_phids,
|
$user_phids,
|
||||||
$xproject_phids,
|
$xproject_phids,
|
||||||
$owner_phids,
|
$owner_phids,
|
||||||
$author_phids);
|
$author_phids,
|
||||||
|
$project_group_phids);
|
||||||
$handles = id(new PhabricatorObjectHandleData($handle_phids))
|
$handles = id(new PhabricatorObjectHandleData($handle_phids))
|
||||||
->loadHandles();
|
->loadHandles();
|
||||||
|
|
||||||
switch ($search_query->getParameter('group')) {
|
switch ($search_query->getParameter('group')) {
|
||||||
case 'priority':
|
case 'priority':
|
||||||
$data = mgroup($data, 'getPriority');
|
$data = mgroup($data, 'getPriority');
|
||||||
krsort($data);
|
|
||||||
|
|
||||||
// If we have invalid priorities, they'll all map to "???". Merge
|
// If we have invalid priorities, they'll all map to "???". Merge
|
||||||
// arrays to prevent them from overwriting each other.
|
// arrays to prevent them from overwriting each other.
|
||||||
|
@ -423,7 +433,6 @@ final class ManiphestTaskListController extends ManiphestController {
|
||||||
break;
|
break;
|
||||||
case 'status':
|
case 'status':
|
||||||
$data = mgroup($data, 'getStatus');
|
$data = mgroup($data, 'getStatus');
|
||||||
ksort($data);
|
|
||||||
|
|
||||||
$out = array();
|
$out = array();
|
||||||
foreach ($data as $status => $tasks) {
|
foreach ($data as $status => $tasks) {
|
||||||
|
@ -452,6 +461,26 @@ final class ManiphestTaskListController extends ManiphestController {
|
||||||
|
|
||||||
ksort($data);
|
ksort($data);
|
||||||
break;
|
break;
|
||||||
|
case 'project':
|
||||||
|
$grouped = array();
|
||||||
|
foreach ($data as $task) {
|
||||||
|
$phids = $task->getProjectPHIDs();
|
||||||
|
if ($project_phids) {
|
||||||
|
// 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;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$data = array(
|
$data = array(
|
||||||
'Tasks' => $data,
|
'Tasks' => $data,
|
||||||
|
@ -527,6 +556,7 @@ final class ManiphestTaskListController extends ManiphestController {
|
||||||
'p' => 'priority',
|
'p' => 'priority',
|
||||||
's' => 'status',
|
's' => 'status',
|
||||||
'o' => 'owner',
|
'o' => 'owner',
|
||||||
|
'j' => 'project',
|
||||||
);
|
);
|
||||||
if (empty($groups[$group])) {
|
if (empty($groups[$group])) {
|
||||||
$group = 'p';
|
$group = 'p';
|
||||||
|
@ -543,6 +573,7 @@ final class ManiphestTaskListController extends ManiphestController {
|
||||||
'p' => 'Priority',
|
'p' => 'Priority',
|
||||||
'o' => 'Owner',
|
'o' => 'Owner',
|
||||||
's' => 'Status',
|
's' => 'Status',
|
||||||
|
'j' => 'Project',
|
||||||
'n' => 'None',
|
'n' => 'None',
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ final class ManiphestTaskQuery {
|
||||||
const GROUP_PRIORITY = 'group-priority';
|
const GROUP_PRIORITY = 'group-priority';
|
||||||
const GROUP_OWNER = 'group-owner';
|
const GROUP_OWNER = 'group-owner';
|
||||||
const GROUP_STATUS = 'group-status';
|
const GROUP_STATUS = 'group-status';
|
||||||
|
const GROUP_PROJECT = 'group-project';
|
||||||
|
|
||||||
private $orderBy = 'order-modified';
|
private $orderBy = 'order-modified';
|
||||||
const ORDER_PRIORITY = 'order-priority';
|
const ORDER_PRIORITY = 'order-priority';
|
||||||
|
@ -224,6 +225,11 @@ final class ManiphestTaskQuery {
|
||||||
$offset = (int)nonempty($this->offset, 0);
|
$offset = (int)nonempty($this->offset, 0);
|
||||||
$limit = (int)nonempty($this->limit, self::DEFAULT_PAGE_SIZE);
|
$limit = (int)nonempty($this->limit, self::DEFAULT_PAGE_SIZE);
|
||||||
|
|
||||||
|
if ($this->groupBy == self::GROUP_PROJECT) {
|
||||||
|
$limit = PHP_INT_MAX;
|
||||||
|
$offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
$data = queryfx_all(
|
$data = queryfx_all(
|
||||||
$conn,
|
$conn,
|
||||||
'SELECT %Q * %Q FROM %T task %Q %Q %Q %Q %Q LIMIT %d, %d',
|
'SELECT %Q * %Q FROM %T task %Q %Q %Q %Q %Q LIMIT %d, %d',
|
||||||
|
@ -247,7 +253,13 @@ final class ManiphestTaskQuery {
|
||||||
$this->rowCount = null;
|
$this->rowCount = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $task_dao->loadAllFromArray($data);
|
$tasks = $task_dao->loadAllFromArray($data);
|
||||||
|
|
||||||
|
if ($this->groupBy == self::GROUP_PROJECT) {
|
||||||
|
$tasks = $this->applyGroupByProject($tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildTaskIDsWhereClause($conn) {
|
private function buildTaskIDsWhereClause($conn) {
|
||||||
|
@ -420,6 +432,10 @@ final class ManiphestTaskQuery {
|
||||||
case self::GROUP_STATUS:
|
case self::GROUP_STATUS:
|
||||||
$order[] = 'status';
|
$order[] = 'status';
|
||||||
break;
|
break;
|
||||||
|
case self::GROUP_PROJECT:
|
||||||
|
// NOTE: We have to load the entire result set and apply this grouping
|
||||||
|
// in the PHP process for now.
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Exception("Unknown group query '{$this->groupBy}'!");
|
throw new Exception("Unknown group query '{$this->groupBy}'!");
|
||||||
}
|
}
|
||||||
|
@ -460,4 +476,69 @@ final class ManiphestTaskQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To get paging to work for "group by project", we need to do a bunch of
|
||||||
|
* server-side magic since there's currently no way to sort by project name on
|
||||||
|
* the database.
|
||||||
|
*
|
||||||
|
* TODO: Move this all to the database.
|
||||||
|
*/
|
||||||
|
private function applyGroupByProject(array $tasks) {
|
||||||
|
|
||||||
|
$project_phids = array();
|
||||||
|
foreach ($tasks as $task) {
|
||||||
|
foreach ($task->getProjectPHIDs() as $phid) {
|
||||||
|
$project_phids[$phid] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$handles = id(new PhabricatorObjectHandleData(array_keys($project_phids)))
|
||||||
|
->loadHandles();
|
||||||
|
|
||||||
|
$max = 1;
|
||||||
|
foreach ($handles as $handle) {
|
||||||
|
$max = max($max, strlen($handle->getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = array();
|
||||||
|
$ii = 0;
|
||||||
|
foreach ($tasks as $key => $task) {
|
||||||
|
$phids = $task->getProjectPHIDs();
|
||||||
|
if ($this->projectPHIDs) {
|
||||||
|
$phids = array_diff($phids, $this->projectPHIDs);
|
||||||
|
}
|
||||||
|
if ($phids) {
|
||||||
|
foreach ($phids as $phid) {
|
||||||
|
$items[] = array(
|
||||||
|
'key' => $key,
|
||||||
|
'seq' => sprintf(
|
||||||
|
'%'.$max.'s%d',
|
||||||
|
$handles[$phid]->getName(),
|
||||||
|
$ii),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Sort "no project" tasks first.
|
||||||
|
$items[] = array(
|
||||||
|
'key' => $key,
|
||||||
|
'seq' => '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
++$ii;
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = isort($items, 'seq');
|
||||||
|
$items = array_slice(
|
||||||
|
$items,
|
||||||
|
nonempty($this->offset),
|
||||||
|
nonempty($this->limit, self::DEFAULT_PAGE_SIZE));
|
||||||
|
|
||||||
|
$result = array();
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$result[] = $tasks[$item['key']];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'applications/maniphest/constants/owner');
|
||||||
phutil_require_module('phabricator', 'applications/maniphest/storage/subscriber');
|
phutil_require_module('phabricator', 'applications/maniphest/storage/subscriber');
|
||||||
phutil_require_module('phabricator', 'applications/maniphest/storage/task');
|
phutil_require_module('phabricator', 'applications/maniphest/storage/task');
|
||||||
phutil_require_module('phabricator', 'applications/maniphest/storage/taskproject');
|
phutil_require_module('phabricator', 'applications/maniphest/storage/taskproject');
|
||||||
|
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
||||||
phutil_require_module('phabricator', 'storage/qsprintf');
|
phutil_require_module('phabricator', 'storage/qsprintf');
|
||||||
phutil_require_module('phabricator', 'storage/queryfx');
|
phutil_require_module('phabricator', 'storage/queryfx');
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue