2011-06-30 01:16:33 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Query tasks by specific criteria. This class uses the higher-performance
|
|
|
|
* but less-general Maniphest indexes to satisfy queries.
|
2011-07-04 22:04:22 +02:00
|
|
|
*
|
|
|
|
* @group maniphest
|
2011-06-30 01:16:33 +02:00
|
|
|
*/
|
2012-10-05 00:30:51 +02:00
|
|
|
final class ManiphestTaskQuery extends PhabricatorQuery {
|
2011-06-30 01:16:33 +02:00
|
|
|
|
2011-12-02 16:30:20 +01:00
|
|
|
private $taskIDs = array();
|
2012-11-01 18:47:45 +01:00
|
|
|
private $taskPHIDs = array();
|
2011-06-30 01:16:33 +02:00
|
|
|
private $authorPHIDs = array();
|
|
|
|
private $ownerPHIDs = array();
|
|
|
|
private $includeUnowned = null;
|
|
|
|
private $projectPHIDs = array();
|
2012-02-29 06:08:02 +01:00
|
|
|
private $xprojectPHIDs = array();
|
2011-07-07 19:24:49 +02:00
|
|
|
private $subscriberPHIDs = array();
|
2012-10-05 00:30:51 +02:00
|
|
|
private $anyProjectPHIDs = array();
|
2012-02-29 06:08:02 +01:00
|
|
|
private $includeNoProject = null;
|
2011-06-30 01:16:33 +02:00
|
|
|
|
2012-06-15 23:09:49 +02:00
|
|
|
private $fullTextSearch = '';
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
private $status = 'status-any';
|
|
|
|
const STATUS_ANY = 'status-any';
|
|
|
|
const STATUS_OPEN = 'status-open';
|
|
|
|
const STATUS_CLOSED = 'status-closed';
|
2012-06-29 18:17:19 +02:00
|
|
|
const STATUS_RESOLVED = 'status-resolved';
|
|
|
|
const STATUS_WONTFIX = 'status-wontfix';
|
|
|
|
const STATUS_INVALID = 'status-invalid';
|
|
|
|
const STATUS_SPITE = 'status-spite';
|
|
|
|
const STATUS_DUPLICATE = 'status-duplicate';
|
2011-06-30 01:16:33 +02:00
|
|
|
|
|
|
|
private $priority = null;
|
|
|
|
|
2012-08-01 07:52:46 +02:00
|
|
|
private $minPriority = null;
|
|
|
|
private $maxPriority = null;
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
private $groupBy = 'group-none';
|
|
|
|
const GROUP_NONE = 'group-none';
|
|
|
|
const GROUP_PRIORITY = 'group-priority';
|
|
|
|
const GROUP_OWNER = 'group-owner';
|
|
|
|
const GROUP_STATUS = '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
|
|
|
const GROUP_PROJECT = 'group-project';
|
2011-06-30 01:16:33 +02:00
|
|
|
|
|
|
|
private $orderBy = 'order-modified';
|
|
|
|
const ORDER_PRIORITY = 'order-priority';
|
|
|
|
const ORDER_CREATED = 'order-created';
|
|
|
|
const ORDER_MODIFIED = 'order-modified';
|
2012-08-02 23:21:13 +02:00
|
|
|
const ORDER_TITLE = 'order-title';
|
2011-06-30 01:16:33 +02:00
|
|
|
|
|
|
|
private $limit = null;
|
|
|
|
const DEFAULT_PAGE_SIZE = 1000;
|
|
|
|
|
|
|
|
private $offset = 0;
|
|
|
|
private $calculateRows = false;
|
|
|
|
|
|
|
|
private $rowCount = null;
|
|
|
|
|
2012-08-08 04:40:38 +02:00
|
|
|
private $groupByProjectResults = null; // See comment at bottom for details
|
2013-02-26 23:59:31 +01:00
|
|
|
private $viewer;
|
2012-08-08 04:40:38 +02:00
|
|
|
|
2013-02-26 23:59:31 +01:00
|
|
|
public function setViewer(PhabricatorUser $viewer) {
|
|
|
|
$this->viewer = $viewer;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getViewer() {
|
|
|
|
return $this->viewer;
|
|
|
|
}
|
2011-06-30 01:16:33 +02:00
|
|
|
|
|
|
|
public function withAuthors(array $authors) {
|
|
|
|
$this->authorPHIDs = $authors;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-12-02 16:30:20 +01:00
|
|
|
public function withTaskIDs(array $ids) {
|
|
|
|
$this->taskIDs = $ids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
public function withTaskPHIDs(array $phids) {
|
|
|
|
$this->taskPHIDs = $phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
public function withOwners(array $owners) {
|
|
|
|
$this->includeUnowned = false;
|
|
|
|
foreach ($owners as $k => $phid) {
|
|
|
|
if ($phid == ManiphestTaskOwner::OWNER_UP_FOR_GRABS) {
|
|
|
|
$this->includeUnowned = true;
|
|
|
|
unset($owners[$k]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$this->ownerPHIDs = $owners;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-10-05 00:31:04 +02:00
|
|
|
public function withAllProjects(array $projects) {
|
2012-02-29 06:08:02 +01:00
|
|
|
$this->includeNoProject = false;
|
|
|
|
foreach ($projects as $k => $phid) {
|
|
|
|
if ($phid == ManiphestTaskOwner::PROJECT_NO_PROJECT) {
|
|
|
|
$this->includeNoProject = true;
|
|
|
|
unset($projects[$k]);
|
|
|
|
}
|
|
|
|
}
|
2011-06-30 01:16:33 +02:00
|
|
|
$this->projectPHIDs = $projects;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-02-29 06:08:02 +01:00
|
|
|
public function withoutProjects(array $projects) {
|
|
|
|
$this->xprojectPHIDs = $projects;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
public function withStatus($status) {
|
|
|
|
$this->status = $status;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function withPriority($priority) {
|
|
|
|
$this->priority = $priority;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-08-01 07:52:46 +02:00
|
|
|
public function withPrioritiesBetween($min, $max) {
|
|
|
|
$this->minPriority = $min;
|
|
|
|
$this->maxPriority = $max;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-07-07 19:24:49 +02:00
|
|
|
public function withSubscribers(array $subscribers) {
|
|
|
|
$this->subscriberPHIDs = $subscribers;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-06-15 23:09:49 +02:00
|
|
|
public function withFullTextSearch($fulltext_search) {
|
|
|
|
$this->fullTextSearch = $fulltext_search;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
public function setGroupBy($group) {
|
|
|
|
$this->groupBy = $group;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setOrderBy($order) {
|
|
|
|
$this->orderBy = $order;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setLimit($limit) {
|
|
|
|
$this->limit = $limit;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setOffset($offset) {
|
|
|
|
$this->offset = $offset;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setCalculateRows($calculate_rows) {
|
|
|
|
$this->calculateRows = $calculate_rows;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getRowCount() {
|
|
|
|
if ($this->rowCount === null) {
|
|
|
|
throw new Exception(
|
|
|
|
"You must execute a query with setCalculateRows() before you can ".
|
|
|
|
"retrieve a row count.");
|
|
|
|
}
|
|
|
|
return $this->rowCount;
|
|
|
|
}
|
|
|
|
|
2012-08-08 04:40:38 +02:00
|
|
|
public function getGroupByProjectResults() {
|
|
|
|
return $this->groupByProjectResults;
|
|
|
|
}
|
|
|
|
|
2012-10-05 00:30:51 +02:00
|
|
|
public function withAnyProjects(array $projects) {
|
|
|
|
$this->anyProjectPHIDs = $projects;
|
2011-07-07 22:50:56 +02:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
public function execute() {
|
|
|
|
|
|
|
|
$task_dao = new ManiphestTask();
|
|
|
|
$conn = $task_dao->establishConnection('r');
|
|
|
|
|
|
|
|
if ($this->calculateRows) {
|
|
|
|
$calc = 'SQL_CALC_FOUND_ROWS';
|
|
|
|
} else {
|
|
|
|
$calc = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
$where = array();
|
2011-12-02 16:30:20 +01:00
|
|
|
$where[] = $this->buildTaskIDsWhereClause($conn);
|
2012-11-01 18:47:45 +01:00
|
|
|
$where[] = $this->buildTaskPHIDsWhereClause($conn);
|
2011-06-30 01:16:33 +02:00
|
|
|
$where[] = $this->buildStatusWhereClause($conn);
|
|
|
|
$where[] = $this->buildPriorityWhereClause($conn);
|
|
|
|
$where[] = $this->buildAuthorWhereClause($conn);
|
|
|
|
$where[] = $this->buildOwnerWhereClause($conn);
|
2011-07-07 19:24:49 +02:00
|
|
|
$where[] = $this->buildSubscriberWhereClause($conn);
|
2011-06-30 01:16:33 +02:00
|
|
|
$where[] = $this->buildProjectWhereClause($conn);
|
2012-10-05 00:30:51 +02:00
|
|
|
$where[] = $this->buildAnyProjectWhereClause($conn);
|
2012-02-29 06:08:02 +01:00
|
|
|
$where[] = $this->buildXProjectWhereClause($conn);
|
2012-06-15 23:09:49 +02:00
|
|
|
$where[] = $this->buildFullTextWhereClause($conn);
|
2011-06-30 01:16:33 +02:00
|
|
|
|
2012-10-05 00:30:51 +02:00
|
|
|
$where = $this->formatWhereClause($where);
|
2011-06-30 01:16:33 +02:00
|
|
|
|
|
|
|
$join = array();
|
|
|
|
$join[] = $this->buildProjectJoinClause($conn);
|
2012-10-05 00:30:51 +02:00
|
|
|
$join[] = $this->buildAnyProjectJoinClause($conn);
|
2012-02-29 06:08:02 +01:00
|
|
|
$join[] = $this->buildXProjectJoinClause($conn);
|
2011-07-07 19:24:49 +02:00
|
|
|
$join[] = $this->buildSubscriberJoinClause($conn);
|
2011-06-30 01:16:33 +02:00
|
|
|
|
|
|
|
$join = array_filter($join);
|
|
|
|
if ($join) {
|
|
|
|
$join = implode(' ', $join);
|
|
|
|
} else {
|
|
|
|
$join = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
$having = '';
|
|
|
|
$count = '';
|
|
|
|
$group = '';
|
|
|
|
|
2012-10-05 00:30:51 +02:00
|
|
|
if (count($this->projectPHIDs) > 1 || count($this->anyProjectPHIDs) > 1) {
|
|
|
|
// If we're joining multiple rows, we need to group the results by the
|
|
|
|
// task IDs.
|
2011-06-30 01:16:33 +02:00
|
|
|
$group = 'GROUP BY task.id';
|
2012-10-05 00:30:51 +02:00
|
|
|
} else {
|
|
|
|
$group = '';
|
|
|
|
}
|
2011-07-07 22:50:56 +02:00
|
|
|
|
2012-10-05 00:30:51 +02:00
|
|
|
if (count($this->projectPHIDs) > 1) {
|
|
|
|
// We want to treat the query as an intersection query, not a union
|
|
|
|
// query. We sum the project count and require it be the same as the
|
|
|
|
// number of projects we're searching for.
|
|
|
|
|
|
|
|
$count = ', COUNT(project.projectPHID) projectCount';
|
|
|
|
$having = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'HAVING projectCount = %d',
|
|
|
|
count($this->projectPHIDs));
|
2011-06-30 01:16:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$order = $this->buildOrderClause($conn);
|
|
|
|
|
|
|
|
$offset = (int)nonempty($this->offset, 0);
|
|
|
|
$limit = (int)nonempty($this->limit, self::DEFAULT_PAGE_SIZE);
|
|
|
|
|
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 ($this->groupBy == self::GROUP_PROJECT) {
|
|
|
|
$limit = PHP_INT_MAX;
|
|
|
|
$offset = 0;
|
|
|
|
}
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
$data = queryfx_all(
|
|
|
|
$conn,
|
|
|
|
'SELECT %Q * %Q FROM %T task %Q %Q %Q %Q %Q LIMIT %d, %d',
|
|
|
|
$calc,
|
|
|
|
$count,
|
|
|
|
$task_dao->getTableName(),
|
|
|
|
$join,
|
|
|
|
$where,
|
|
|
|
$group,
|
|
|
|
$having,
|
|
|
|
$order,
|
|
|
|
$offset,
|
|
|
|
$limit);
|
|
|
|
|
|
|
|
if ($this->calculateRows) {
|
|
|
|
$count = queryfx_one(
|
|
|
|
$conn,
|
|
|
|
'SELECT FOUND_ROWS() N');
|
|
|
|
$this->rowCount = $count['N'];
|
|
|
|
} else {
|
|
|
|
$this->rowCount = null;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
$tasks = $task_dao->loadAllFromArray($data);
|
|
|
|
|
|
|
|
if ($this->groupBy == self::GROUP_PROJECT) {
|
|
|
|
$tasks = $this->applyGroupByProject($tasks);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $tasks;
|
2011-06-30 01:16:33 +02:00
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildTaskIDsWhereClause(AphrontDatabaseConnection $conn) {
|
2011-12-02 16:30:20 +01:00
|
|
|
if (!$this->taskIDs) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'id in (%Ld)',
|
|
|
|
$this->taskIDs);
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildTaskPHIDsWhereClause(AphrontDatabaseConnection $conn) {
|
|
|
|
if (!$this->taskPHIDs) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'phid in (%Ls)',
|
|
|
|
$this->taskPHIDs);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function buildStatusWhereClause(AphrontDatabaseConnection $conn) {
|
2012-06-29 18:17:19 +02:00
|
|
|
|
|
|
|
static $map = array(
|
|
|
|
self::STATUS_RESOLVED => ManiphestTaskStatus::STATUS_CLOSED_RESOLVED,
|
|
|
|
self::STATUS_WONTFIX => ManiphestTaskStatus::STATUS_CLOSED_WONTFIX,
|
|
|
|
self::STATUS_INVALID => ManiphestTaskStatus::STATUS_CLOSED_INVALID,
|
|
|
|
self::STATUS_SPITE => ManiphestTaskStatus::STATUS_CLOSED_SPITE,
|
|
|
|
self::STATUS_DUPLICATE => ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE,
|
|
|
|
);
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
switch ($this->status) {
|
|
|
|
case self::STATUS_ANY:
|
|
|
|
return null;
|
|
|
|
case self::STATUS_OPEN:
|
|
|
|
return 'status = 0';
|
|
|
|
case self::STATUS_CLOSED:
|
|
|
|
return 'status > 0';
|
|
|
|
default:
|
2012-06-29 18:17:19 +02:00
|
|
|
$constant = idx($map, $this->status);
|
|
|
|
if (!$constant) {
|
|
|
|
throw new Exception("Unknown status query '{$this->status}'!");
|
|
|
|
}
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'status = %d',
|
|
|
|
$constant);
|
2011-06-30 01:16:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildPriorityWhereClause(AphrontDatabaseConnection $conn) {
|
2012-08-01 07:52:46 +02:00
|
|
|
if ($this->priority !== null) {
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'priority = %d',
|
|
|
|
$this->priority);
|
|
|
|
} elseif ($this->minPriority !== null && $this->maxPriority !== null) {
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'priority >= %d AND priority <= %d',
|
|
|
|
$this->minPriority,
|
|
|
|
$this->maxPriority);
|
2011-06-30 01:16:33 +02:00
|
|
|
}
|
|
|
|
|
2012-08-01 07:52:46 +02:00
|
|
|
return null;
|
2011-06-30 01:16:33 +02:00
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildAuthorWhereClause(AphrontDatabaseConnection $conn) {
|
2011-06-30 01:16:33 +02:00
|
|
|
if (!$this->authorPHIDs) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'authorPHID in (%Ls)',
|
|
|
|
$this->authorPHIDs);
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildOwnerWhereClause(AphrontDatabaseConnection $conn) {
|
2011-06-30 01:16:33 +02:00
|
|
|
if (!$this->ownerPHIDs) {
|
|
|
|
if ($this->includeUnowned === null) {
|
|
|
|
return null;
|
|
|
|
} else if ($this->includeUnowned) {
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'ownerPHID IS NULL');
|
|
|
|
} else {
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'ownerPHID IS NOT NULL');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->includeUnowned) {
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'ownerPHID IN (%Ls) OR ownerPHID IS NULL',
|
|
|
|
$this->ownerPHIDs);
|
|
|
|
} else {
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'ownerPHID IN (%Ls)',
|
|
|
|
$this->ownerPHIDs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildFullTextWhereClause(AphrontDatabaseConnection $conn) {
|
2012-06-15 23:09:49 +02:00
|
|
|
if (!$this->fullTextSearch) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In doing a fulltext search, we first find all the PHIDs that match the
|
|
|
|
// fulltext search, and then use that to limit the rest of the search
|
|
|
|
$fulltext_query = new PhabricatorSearchQuery();
|
|
|
|
$fulltext_query->setQuery($this->fullTextSearch);
|
2012-06-16 00:41:20 +02:00
|
|
|
$fulltext_query->setParameter('limit', PHP_INT_MAX);
|
2012-06-15 23:09:49 +02:00
|
|
|
|
|
|
|
$engine = PhabricatorSearchEngineSelector::newSelector()->newEngine();
|
|
|
|
$fulltext_results = $engine->executeSearch($fulltext_query);
|
|
|
|
|
2012-06-16 00:41:20 +02:00
|
|
|
if (empty($fulltext_results)) {
|
|
|
|
$fulltext_results = array(null);
|
|
|
|
}
|
|
|
|
|
2012-06-15 23:09:49 +02:00
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'phid IN (%Ls)',
|
|
|
|
$fulltext_results);
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildSubscriberWhereClause(AphrontDatabaseConnection $conn) {
|
2011-07-07 19:24:49 +02:00
|
|
|
if (!$this->subscriberPHIDs) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'subscriber.subscriberPHID IN (%Ls)',
|
|
|
|
$this->subscriberPHIDs);
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildProjectWhereClause(AphrontDatabaseConnection $conn) {
|
2012-02-29 06:08:02 +01:00
|
|
|
if (!$this->projectPHIDs && !$this->includeNoProject) {
|
2011-06-30 01:16:33 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-02-29 06:08:02 +01:00
|
|
|
$parts = array();
|
|
|
|
if ($this->projectPHIDs) {
|
|
|
|
$parts[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'project.projectPHID in (%Ls)',
|
|
|
|
$this->projectPHIDs);
|
|
|
|
}
|
|
|
|
if ($this->includeNoProject) {
|
|
|
|
$parts[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'project.projectPHID IS NULL');
|
|
|
|
}
|
|
|
|
|
|
|
|
return '('.implode(') OR (', $parts).')';
|
2011-06-30 01:16:33 +02:00
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildProjectJoinClause(AphrontDatabaseConnection $conn) {
|
2012-02-29 06:08:02 +01:00
|
|
|
if (!$this->projectPHIDs && !$this->includeNoProject) {
|
2011-06-30 01:16:33 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$project_dao = new ManiphestTaskProject();
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
2012-02-29 06:08:02 +01:00
|
|
|
'%Q JOIN %T project ON project.taskPHID = task.phid',
|
|
|
|
($this->includeNoProject ? 'LEFT' : ''),
|
2011-06-30 01:16:33 +02:00
|
|
|
$project_dao->getTableName());
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildAnyProjectWhereClause(AphrontDatabaseConnection $conn) {
|
2012-10-05 00:30:51 +02:00
|
|
|
if (!$this->anyProjectPHIDs) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'anyproject.projectPHID IN (%Ls)',
|
|
|
|
$this->anyProjectPHIDs);
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildAnyProjectJoinClause(AphrontDatabaseConnection $conn) {
|
2012-10-05 00:30:51 +02:00
|
|
|
if (!$this->anyProjectPHIDs) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$project_dao = new ManiphestTaskProject();
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
2012-10-05 00:31:04 +02:00
|
|
|
'JOIN %T anyproject ON anyproject.taskPHID = task.phid',
|
2012-10-05 00:30:51 +02:00
|
|
|
$project_dao->getTableName());
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildXProjectWhereClause(AphrontDatabaseConnection $conn) {
|
2012-02-29 06:08:02 +01:00
|
|
|
if (!$this->xprojectPHIDs) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'xproject.projectPHID IS NULL');
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildXProjectJoinClause(AphrontDatabaseConnection $conn) {
|
2012-02-29 06:08:02 +01:00
|
|
|
if (!$this->xprojectPHIDs) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$project_dao = new ManiphestTaskProject();
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'LEFT JOIN %T xproject ON xproject.taskPHID = task.phid
|
|
|
|
AND xproject.projectPHID IN (%Ls)',
|
|
|
|
$project_dao->getTableName(),
|
|
|
|
$this->xprojectPHIDs);
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildSubscriberJoinClause(AphrontDatabaseConnection $conn) {
|
2011-07-07 19:24:49 +02:00
|
|
|
if (!$this->subscriberPHIDs) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$subscriber_dao = new ManiphestTaskSubscriber();
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
|
|
|
'JOIN %T subscriber ON subscriber.taskPHID = task.phid',
|
|
|
|
$subscriber_dao->getTableName());
|
|
|
|
}
|
|
|
|
|
2012-11-01 18:47:45 +01:00
|
|
|
private function buildOrderClause(AphrontDatabaseConnection $conn) {
|
2011-06-30 01:16:33 +02:00
|
|
|
$order = array();
|
|
|
|
|
|
|
|
switch ($this->groupBy) {
|
|
|
|
case self::GROUP_NONE:
|
|
|
|
break;
|
|
|
|
case self::GROUP_PRIORITY:
|
|
|
|
$order[] = 'priority';
|
|
|
|
break;
|
|
|
|
case self::GROUP_OWNER:
|
|
|
|
$order[] = 'ownerOrdering';
|
|
|
|
break;
|
|
|
|
case self::GROUP_STATUS:
|
|
|
|
$order[] = 'status';
|
|
|
|
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 self::GROUP_PROJECT:
|
|
|
|
// NOTE: We have to load the entire result set and apply this grouping
|
|
|
|
// in the PHP process for now.
|
|
|
|
break;
|
2011-06-30 01:16:33 +02:00
|
|
|
default:
|
|
|
|
throw new Exception("Unknown group query '{$this->groupBy}'!");
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ($this->orderBy) {
|
|
|
|
case self::ORDER_PRIORITY:
|
|
|
|
$order[] = 'priority';
|
2012-04-02 21:12:04 +02:00
|
|
|
$order[] = 'subpriority';
|
2011-06-30 01:16:33 +02:00
|
|
|
$order[] = 'dateModified';
|
|
|
|
break;
|
|
|
|
case self::ORDER_CREATED:
|
|
|
|
$order[] = 'id';
|
|
|
|
break;
|
|
|
|
case self::ORDER_MODIFIED:
|
|
|
|
$order[] = 'dateModified';
|
|
|
|
break;
|
2012-08-02 23:21:13 +02:00
|
|
|
case self::ORDER_TITLE:
|
|
|
|
$order[] = 'title';
|
|
|
|
break;
|
2011-06-30 01:16:33 +02:00
|
|
|
default:
|
|
|
|
throw new Exception("Unknown order query '{$this->orderBy}'!");
|
|
|
|
}
|
|
|
|
|
|
|
|
$order = array_unique($order);
|
|
|
|
|
|
|
|
if (empty($order)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($order as $k => $column) {
|
|
|
|
switch ($column) {
|
2012-04-02 21:12:04 +02:00
|
|
|
case 'subpriority':
|
2011-06-30 01:16:33 +02:00
|
|
|
case 'ownerOrdering':
|
2012-08-02 23:21:13 +02:00
|
|
|
case 'title':
|
2011-06-30 01:16:33 +02:00
|
|
|
$order[$k] = "task.{$column} ASC";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
$order[$k] = "task.{$column} DESC";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 'ORDER BY '.implode(', ', $order);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
2012-08-08 04:40:38 +02:00
|
|
|
* As a consequence of this, moreover, because the list we return from here
|
|
|
|
* may include a single task multiple times (once for each project it's in),
|
|
|
|
* sorting gets screwed up in the controller unless we tell it which project
|
|
|
|
* to put the task in each time it appears. Hence the magic field
|
|
|
|
* groupByProjectResults.
|
|
|
|
*
|
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
|
|
|
* TODO: Move this all to the database.
|
|
|
|
*/
|
|
|
|
private function applyGroupByProject(array $tasks) {
|
2012-04-03 21:10:45 +02:00
|
|
|
assert_instances_of($tasks, 'ManiphestTask');
|
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_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();
|
2012-10-05 00:30:51 +02:00
|
|
|
if ($this->projectPHIDs) {
|
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
|
|
|
$phids = array_diff($phids, $this->projectPHIDs);
|
|
|
|
}
|
|
|
|
if ($phids) {
|
|
|
|
foreach ($phids as $phid) {
|
|
|
|
$items[] = array(
|
2012-08-08 04:40:38 +02:00
|
|
|
'key' => $key,
|
|
|
|
'proj' => $phid,
|
|
|
|
'seq' => sprintf(
|
|
|
|
'%'.$max.'s%09d',
|
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
|
|
|
$handles[$phid]->getName(),
|
|
|
|
$ii),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Sort "no project" tasks first.
|
|
|
|
$items[] = array(
|
2012-08-08 04:40:38 +02:00
|
|
|
'key' => $key,
|
|
|
|
'proj' => null,
|
|
|
|
'seq' => sprintf(
|
|
|
|
'%'.$max.'s%09d',
|
|
|
|
'',
|
|
|
|
$ii),
|
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
|
|
|
);
|
|
|
|
}
|
|
|
|
++$ii;
|
|
|
|
}
|
|
|
|
|
|
|
|
$items = isort($items, 'seq');
|
|
|
|
$items = array_slice(
|
|
|
|
$items,
|
|
|
|
nonempty($this->offset),
|
|
|
|
nonempty($this->limit, self::DEFAULT_PAGE_SIZE));
|
|
|
|
|
|
|
|
$result = array();
|
2012-08-08 04:40:38 +02:00
|
|
|
$projects = array();
|
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
|
|
|
foreach ($items as $item) {
|
2012-08-08 04:40:38 +02:00
|
|
|
$result[] = $projects[$item['proj']][] = $tasks[$item['key']];
|
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
|
|
|
}
|
2012-08-08 04:40:38 +02:00
|
|
|
$this->groupByProjectResults = $projects;
|
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
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2011-06-30 01:16:33 +02:00
|
|
|
}
|