2011-06-29 16:16:33 -07:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Query tasks by specific criteria. This class uses the higher-performance
|
|
|
|
* but less-general Maniphest indexes to satisfy queries.
|
|
|
|
*/
|
2014-07-10 08:12:48 +10:00
|
|
|
final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
2011-06-29 16:16:33 -07:00
|
|
|
|
2015-06-22 11:54:51 -07:00
|
|
|
private $taskIDs;
|
|
|
|
private $taskPHIDs;
|
|
|
|
private $authorPHIDs;
|
|
|
|
private $ownerPHIDs;
|
2015-04-23 15:44:27 -07:00
|
|
|
private $noOwner;
|
|
|
|
private $anyOwner;
|
2015-06-22 11:54:51 -07:00
|
|
|
private $subscriberPHIDs;
|
2013-09-12 13:03:39 -07:00
|
|
|
private $dateCreatedAfter;
|
|
|
|
private $dateCreatedBefore;
|
2014-03-17 15:53:07 -07:00
|
|
|
private $dateModifiedAfter;
|
|
|
|
private $dateModifiedBefore;
|
2016-03-09 08:40:19 -08:00
|
|
|
private $bridgedObjectPHIDs;
|
2016-07-28 09:18:47 -07:00
|
|
|
private $hasOpenParents;
|
|
|
|
private $hasOpenSubtasks;
|
2016-07-28 10:47:40 -07:00
|
|
|
private $parentTaskIDs;
|
|
|
|
private $subtaskIDs;
|
2017-03-01 15:37:26 -08:00
|
|
|
private $subtypes;
|
2018-02-08 15:13:03 -08:00
|
|
|
private $closedEpochMin;
|
|
|
|
private $closedEpochMax;
|
|
|
|
private $closerPHIDs;
|
Allow Maniphest tasks to be queried by workboard Column PHID via SearchEngine
Summary:
Ref T13120. See PHI571. Fixes T5024. This adds a "View as Query" action to workboard columns, which builds a query in Maniphest that has the current query constraints plus an additional constraint to select only tasks in the specified column.
This is a normal query and can be turned into a dashboard panel, added to a menu, edited, saved as a link, etc.
Much of the complexity here is that finding tasks in a given column isn't entirely straightforward because of how board layout works: when you create a task, it isn't immediately placed in columns. It's only actually added to the "Backlog" column on any boards when someone looks at the board.
To get the right behavior, we must do "board layout" for any queried columns before we can constrain results. This isn't enormously efficient, but should be OK for reasonable boards.
Test Plan:
- Used "View as Query" for normal columns and milestome columns, got appropriate queries in Maniphest.
- Applied filters to the board (e.g., "Priorities: wishlist"), then used "View As Query" and had my custom filters respected.
- Queried some large boards/columns with more than a thousand tasks, got results back within a second or so.
Reviewers: amckinley
Reviewed By: amckinley
Maniphest Tasks: T13120, T5024
Differential Revision: https://secure.phabricator.com/D19366
2018-04-13 06:20:09 -07:00
|
|
|
private $columnPHIDs;
|
2011-06-29 16:16:33 -07:00
|
|
|
|
|
|
|
private $status = 'status-any';
|
|
|
|
const STATUS_ANY = 'status-any';
|
|
|
|
const STATUS_OPEN = 'status-open';
|
|
|
|
const STATUS_CLOSED = 'status-closed';
|
2012-06-29 09:17:19 -07: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-29 16:16:33 -07:00
|
|
|
|
2013-09-10 11:07:34 -07:00
|
|
|
private $statuses;
|
2013-09-10 11:54:17 -07:00
|
|
|
private $priorities;
|
2015-03-26 11:11:23 -07:00
|
|
|
private $subpriorities;
|
2011-06-29 16:16:33 -07: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-19 19:47:34 -07:00
|
|
|
const GROUP_PROJECT = 'group-project';
|
2011-06-29 16:16:33 -07:00
|
|
|
|
|
|
|
const ORDER_PRIORITY = 'order-priority';
|
|
|
|
const ORDER_CREATED = 'order-created';
|
|
|
|
const ORDER_MODIFIED = 'order-modified';
|
2012-08-02 14:21:13 -07:00
|
|
|
const ORDER_TITLE = 'order-title';
|
2011-06-29 16:16:33 -07:00
|
|
|
|
2014-12-10 16:27:30 -08:00
|
|
|
private $needSubscriberPHIDs;
|
2014-12-18 13:53:45 -08:00
|
|
|
private $needProjectPHIDs;
|
2015-02-03 13:53:35 -08:00
|
|
|
|
2011-06-29 16:16:33 -07:00
|
|
|
public function withAuthors(array $authors) {
|
|
|
|
$this->authorPHIDs = $authors;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-07-21 12:05:28 -07:00
|
|
|
public function withIDs(array $ids) {
|
2013-07-24 17:31:49 -07:00
|
|
|
$this->taskIDs = $ids;
|
2013-07-21 12:05:28 -07:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function withPHIDs(array $phids) {
|
|
|
|
$this->taskPHIDs = $phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-06-29 16:16:33 -07:00
|
|
|
public function withOwners(array $owners) {
|
2015-04-23 03:07:24 -07:00
|
|
|
$no_owner = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN;
|
2015-04-23 15:44:27 -07:00
|
|
|
$any_owner = PhabricatorPeopleAnyOwnerDatasource::FUNCTION_TOKEN;
|
2015-04-19 08:23:56 -07:00
|
|
|
|
2011-06-29 16:16:33 -07:00
|
|
|
foreach ($owners as $k => $phid) {
|
2015-04-19 08:23:56 -07:00
|
|
|
if ($phid === $no_owner || $phid === null) {
|
2015-04-23 15:44:27 -07:00
|
|
|
$this->noOwner = true;
|
|
|
|
unset($owners[$k]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if ($phid === $any_owner) {
|
|
|
|
$this->anyOwner = true;
|
2011-06-29 16:16:33 -07:00
|
|
|
unset($owners[$k]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$this->ownerPHIDs = $owners;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function withStatus($status) {
|
|
|
|
$this->status = $status;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-09-10 11:07:34 -07:00
|
|
|
public function withStatuses(array $statuses) {
|
|
|
|
$this->statuses = $statuses;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-09-10 11:54:17 -07:00
|
|
|
public function withPriorities(array $priorities) {
|
|
|
|
$this->priorities = $priorities;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2015-03-26 11:11:23 -07:00
|
|
|
public function withSubpriorities(array $subpriorities) {
|
|
|
|
$this->subpriorities = $subpriorities;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-07-07 10:24:49 -07:00
|
|
|
public function withSubscribers(array $subscribers) {
|
|
|
|
$this->subscriberPHIDs = $subscribers;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-06-29 16:16:33 -07:00
|
|
|
public function setGroupBy($group) {
|
|
|
|
$this->groupBy = $group;
|
|
|
|
|
2015-06-08 12:23:13 -07:00
|
|
|
switch ($this->groupBy) {
|
|
|
|
case self::GROUP_NONE:
|
|
|
|
$vector = array();
|
|
|
|
break;
|
|
|
|
case self::GROUP_PRIORITY:
|
|
|
|
$vector = array('priority');
|
|
|
|
break;
|
|
|
|
case self::GROUP_OWNER:
|
|
|
|
$vector = array('owner');
|
|
|
|
break;
|
|
|
|
case self::GROUP_STATUS:
|
|
|
|
$vector = array('status');
|
|
|
|
break;
|
|
|
|
case self::GROUP_PROJECT:
|
|
|
|
$vector = array('project');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->setGroupVector($vector);
|
|
|
|
|
2011-06-29 16:16:33 -07:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-07-28 09:18:47 -07:00
|
|
|
public function withOpenSubtasks($value) {
|
|
|
|
$this->hasOpenSubtasks = $value;
|
2015-01-12 13:42:37 -08:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-07-28 09:18:47 -07:00
|
|
|
public function withOpenParents($value) {
|
|
|
|
$this->hasOpenParents = $value;
|
2015-01-12 13:42:37 -08:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-07-28 10:47:40 -07:00
|
|
|
public function withParentTaskIDs(array $ids) {
|
|
|
|
$this->parentTaskIDs = $ids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function withSubtaskIDs(array $ids) {
|
|
|
|
$this->subtaskIDs = $ids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-09-12 13:03:39 -07:00
|
|
|
public function withDateCreatedBefore($date_created_before) {
|
|
|
|
$this->dateCreatedBefore = $date_created_before;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function withDateCreatedAfter($date_created_after) {
|
|
|
|
$this->dateCreatedAfter = $date_created_after;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2014-03-17 15:53:07 -07:00
|
|
|
public function withDateModifiedBefore($date_modified_before) {
|
|
|
|
$this->dateModifiedBefore = $date_modified_before;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function withDateModifiedAfter($date_modified_after) {
|
|
|
|
$this->dateModifiedAfter = $date_modified_after;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2018-02-08 15:13:03 -08:00
|
|
|
public function withClosedEpochBetween($min, $max) {
|
|
|
|
$this->closedEpochMin = $min;
|
|
|
|
$this->closedEpochMax = $max;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function withCloserPHIDs(array $phids) {
|
|
|
|
$this->closerPHIDs = $phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2014-12-10 16:27:30 -08:00
|
|
|
public function needSubscriberPHIDs($bool) {
|
|
|
|
$this->needSubscriberPHIDs = $bool;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2014-12-18 13:53:45 -08:00
|
|
|
public function needProjectPHIDs($bool) {
|
|
|
|
$this->needProjectPHIDs = $bool;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-03-09 08:40:19 -08:00
|
|
|
public function withBridgedObjectPHIDs(array $phids) {
|
|
|
|
$this->bridgedObjectPHIDs = $phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2017-03-01 15:37:26 -08:00
|
|
|
public function withSubtypes(array $subtypes) {
|
|
|
|
$this->subtypes = $subtypes;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
Allow Maniphest tasks to be queried by workboard Column PHID via SearchEngine
Summary:
Ref T13120. See PHI571. Fixes T5024. This adds a "View as Query" action to workboard columns, which builds a query in Maniphest that has the current query constraints plus an additional constraint to select only tasks in the specified column.
This is a normal query and can be turned into a dashboard panel, added to a menu, edited, saved as a link, etc.
Much of the complexity here is that finding tasks in a given column isn't entirely straightforward because of how board layout works: when you create a task, it isn't immediately placed in columns. It's only actually added to the "Backlog" column on any boards when someone looks at the board.
To get the right behavior, we must do "board layout" for any queried columns before we can constrain results. This isn't enormously efficient, but should be OK for reasonable boards.
Test Plan:
- Used "View as Query" for normal columns and milestome columns, got appropriate queries in Maniphest.
- Applied filters to the board (e.g., "Priorities: wishlist"), then used "View As Query" and had my custom filters respected.
- Queried some large boards/columns with more than a thousand tasks, got results back within a second or so.
Reviewers: amckinley
Reviewed By: amckinley
Maniphest Tasks: T13120, T5024
Differential Revision: https://secure.phabricator.com/D19366
2018-04-13 06:20:09 -07:00
|
|
|
public function withColumnPHIDs(array $column_phids) {
|
|
|
|
$this->columnPHIDs = $column_phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2015-06-08 12:21:48 -07:00
|
|
|
public function newResultObject() {
|
2015-04-12 13:01:21 -07:00
|
|
|
return new ManiphestTask();
|
|
|
|
}
|
|
|
|
|
2015-01-14 06:56:07 +11:00
|
|
|
protected function loadPage() {
|
2011-06-29 16:16:33 -07:00
|
|
|
$task_dao = new ManiphestTask();
|
|
|
|
$conn = $task_dao->establishConnection('r');
|
|
|
|
|
2015-06-22 11:54:51 -07:00
|
|
|
$where = $this->buildWhereClause($conn);
|
2011-06-29 16:16:33 -07:00
|
|
|
|
2013-09-12 13:08:25 -07:00
|
|
|
$group_column = '';
|
|
|
|
switch ($this->groupBy) {
|
|
|
|
case self::GROUP_PROJECT:
|
|
|
|
$group_column = qsprintf(
|
|
|
|
$conn,
|
|
|
|
', projectGroupName.indexedObjectPHID projectGroupPHID');
|
|
|
|
break;
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
}
|
|
|
|
|
2013-09-12 13:08:25 -07:00
|
|
|
$rows = queryfx_all(
|
2011-06-29 16:16:33 -07:00
|
|
|
$conn,
|
2015-04-23 04:10:39 -07:00
|
|
|
'%Q %Q FROM %T task %Q %Q %Q %Q %Q %Q',
|
2015-04-23 03:58:04 -07:00
|
|
|
$this->buildSelectClause($conn),
|
2013-09-12 13:08:25 -07:00
|
|
|
$group_column,
|
2011-06-29 16:16:33 -07:00
|
|
|
$task_dao->getTableName(),
|
2015-04-23 03:58:04 -07:00
|
|
|
$this->buildJoinClause($conn),
|
2011-06-29 16:16:33 -07:00
|
|
|
$where,
|
2013-09-12 13:08:25 -07:00
|
|
|
$this->buildGroupClause($conn),
|
2015-04-23 03:58:04 -07:00
|
|
|
$this->buildHavingClause($conn),
|
2015-04-12 13:01:21 -07:00
|
|
|
$this->buildOrderClause($conn),
|
2013-09-10 07:53:27 -07:00
|
|
|
$this->buildLimitClause($conn));
|
2011-06-29 16:16:33 -07:00
|
|
|
|
2013-09-12 13:08:25 -07:00
|
|
|
switch ($this->groupBy) {
|
|
|
|
case self::GROUP_PROJECT:
|
|
|
|
$data = ipull($rows, null, 'id');
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
$data = $rows;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-09-07 09:09:39 -07:00
|
|
|
$data = $this->didLoadRawRows($data);
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
$tasks = $task_dao->loadAllFromArray($data);
|
|
|
|
|
2013-09-12 13:08:25 -07:00
|
|
|
switch ($this->groupBy) {
|
|
|
|
case self::GROUP_PROJECT:
|
|
|
|
$results = array();
|
|
|
|
foreach ($rows as $row) {
|
|
|
|
$task = clone $tasks[$row['id']];
|
|
|
|
$task->attachGroupByProjectPHID($row['projectGroupPHID']);
|
|
|
|
$results[] = $task;
|
|
|
|
}
|
|
|
|
$tasks = $results;
|
|
|
|
break;
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return $tasks;
|
2011-06-29 16:16:33 -07:00
|
|
|
}
|
|
|
|
|
2013-09-13 07:32:57 -07:00
|
|
|
protected function willFilterPage(array $tasks) {
|
|
|
|
if ($this->groupBy == self::GROUP_PROJECT) {
|
|
|
|
// We should only return project groups which the user can actually see.
|
|
|
|
$project_phids = mpull($tasks, 'getGroupByProjectPHID');
|
|
|
|
$projects = id(new PhabricatorProjectQuery())
|
|
|
|
->setViewer($this->getViewer())
|
|
|
|
->withPHIDs($project_phids)
|
|
|
|
->execute();
|
|
|
|
$projects = mpull($projects, null, 'getPHID');
|
|
|
|
|
|
|
|
foreach ($tasks as $key => $task) {
|
2014-05-16 08:47:06 -07:00
|
|
|
if (!$task->getGroupByProjectPHID()) {
|
2016-01-24 09:39:53 -08:00
|
|
|
// This task is either not tagged with any projects, or only tagged
|
|
|
|
// with projects which we're ignoring because they're being queried
|
|
|
|
// for explicitly.
|
2014-05-16 08:47:06 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-09-13 07:32:57 -07:00
|
|
|
if (empty($projects[$task->getGroupByProjectPHID()])) {
|
|
|
|
unset($tasks[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $tasks;
|
|
|
|
}
|
|
|
|
|
2014-07-17 15:42:53 -07:00
|
|
|
protected function didFilterPage(array $tasks) {
|
2014-12-10 16:27:30 -08:00
|
|
|
$phids = mpull($tasks, 'getPHID');
|
|
|
|
|
2014-12-18 13:53:45 -08:00
|
|
|
if ($this->needProjectPHIDs) {
|
|
|
|
$edge_query = id(new PhabricatorEdgeQuery())
|
|
|
|
->withSourcePHIDs($phids)
|
|
|
|
->withEdgeTypes(
|
|
|
|
array(
|
|
|
|
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
|
|
|
));
|
|
|
|
$edge_query->execute();
|
|
|
|
|
|
|
|
foreach ($tasks as $task) {
|
|
|
|
$project_phids = $edge_query->getDestinationPHIDs(
|
|
|
|
array($task->getPHID()));
|
|
|
|
$task->attachProjectPHIDs($project_phids);
|
|
|
|
}
|
2014-12-10 16:27:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->needSubscriberPHIDs) {
|
|
|
|
$subscriber_sets = id(new PhabricatorSubscribersQuery())
|
|
|
|
->withObjectPHIDs($phids)
|
|
|
|
->execute();
|
|
|
|
foreach ($tasks as $task) {
|
|
|
|
$subscribers = idx($subscriber_sets, $task->getPHID(), array());
|
|
|
|
$task->attachSubscriberPHIDs($subscribers);
|
|
|
|
}
|
2014-07-17 15:42:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return $tasks;
|
|
|
|
}
|
|
|
|
|
2015-06-22 11:54:51 -07:00
|
|
|
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
|
|
|
$where = parent::buildWhereClauseParts($conn);
|
|
|
|
|
|
|
|
$where[] = $this->buildStatusWhereClause($conn);
|
|
|
|
$where[] = $this->buildOwnerWhereClause($conn);
|
|
|
|
|
|
|
|
if ($this->taskIDs !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.id in (%Ld)',
|
|
|
|
$this->taskIDs);
|
2011-12-02 07:30:20 -08:00
|
|
|
}
|
|
|
|
|
2015-06-22 11:54:51 -07:00
|
|
|
if ($this->taskPHIDs !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.phid in (%Ls)',
|
|
|
|
$this->taskPHIDs);
|
|
|
|
}
|
2011-12-02 07:30:20 -08:00
|
|
|
|
2015-06-22 11:54:51 -07:00
|
|
|
if ($this->statuses !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.status IN (%Ls)',
|
|
|
|
$this->statuses);
|
2012-11-01 10:47:45 -07:00
|
|
|
}
|
|
|
|
|
2015-06-22 11:54:51 -07:00
|
|
|
if ($this->authorPHIDs !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.authorPHID in (%Ls)',
|
|
|
|
$this->authorPHIDs);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->dateCreatedAfter) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.dateCreated >= %d',
|
|
|
|
$this->dateCreatedAfter);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->dateCreatedBefore) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.dateCreated <= %d',
|
|
|
|
$this->dateCreatedBefore);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->dateModifiedAfter) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.dateModified >= %d',
|
|
|
|
$this->dateModifiedAfter);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->dateModifiedBefore) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.dateModified <= %d',
|
|
|
|
$this->dateModifiedBefore);
|
|
|
|
}
|
|
|
|
|
2018-02-08 15:13:03 -08:00
|
|
|
if ($this->closedEpochMin !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.closedEpoch >= %d',
|
|
|
|
$this->closedEpochMin);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->closedEpochMax !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.closedEpoch <= %d',
|
|
|
|
$this->closedEpochMax);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->closerPHIDs !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.closerPHID IN (%Ls)',
|
|
|
|
$this->closerPHIDs);
|
|
|
|
}
|
|
|
|
|
2015-06-22 11:54:51 -07:00
|
|
|
if ($this->priorities !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.priority IN (%Ld)',
|
|
|
|
$this->priorities);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->subpriorities !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.subpriority IN (%Lf)',
|
|
|
|
$this->subpriorities);
|
|
|
|
}
|
|
|
|
|
2016-03-09 08:40:19 -08:00
|
|
|
if ($this->bridgedObjectPHIDs !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.bridgedObjectPHID IN (%Ls)',
|
|
|
|
$this->bridgedObjectPHIDs);
|
|
|
|
}
|
|
|
|
|
2017-03-01 15:37:26 -08:00
|
|
|
if ($this->subtypes !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.subtype IN (%Ls)',
|
|
|
|
$this->subtypes);
|
|
|
|
}
|
|
|
|
|
Allow Maniphest tasks to be queried by workboard Column PHID via SearchEngine
Summary:
Ref T13120. See PHI571. Fixes T5024. This adds a "View as Query" action to workboard columns, which builds a query in Maniphest that has the current query constraints plus an additional constraint to select only tasks in the specified column.
This is a normal query and can be turned into a dashboard panel, added to a menu, edited, saved as a link, etc.
Much of the complexity here is that finding tasks in a given column isn't entirely straightforward because of how board layout works: when you create a task, it isn't immediately placed in columns. It's only actually added to the "Backlog" column on any boards when someone looks at the board.
To get the right behavior, we must do "board layout" for any queried columns before we can constrain results. This isn't enormously efficient, but should be OK for reasonable boards.
Test Plan:
- Used "View as Query" for normal columns and milestome columns, got appropriate queries in Maniphest.
- Applied filters to the board (e.g., "Priorities: wishlist"), then used "View As Query" and had my custom filters respected.
- Queried some large boards/columns with more than a thousand tasks, got results back within a second or so.
Reviewers: amckinley
Reviewed By: amckinley
Maniphest Tasks: T13120, T5024
Differential Revision: https://secure.phabricator.com/D19366
2018-04-13 06:20:09 -07:00
|
|
|
|
|
|
|
if ($this->columnPHIDs !== null) {
|
|
|
|
$viewer = $this->getViewer();
|
|
|
|
|
|
|
|
$columns = id(new PhabricatorProjectColumnQuery())
|
|
|
|
->setParentQuery($this)
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withPHIDs($this->columnPHIDs)
|
|
|
|
->execute();
|
|
|
|
if (!$columns) {
|
|
|
|
throw new PhabricatorEmptyQueryException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// We must do board layout before we move forward because the column
|
|
|
|
// positions may not yet exist otherwise. An example is that newly
|
|
|
|
// created tasks may not yet be positioned in the backlog column.
|
|
|
|
|
|
|
|
$projects = mpull($columns, 'getProject');
|
|
|
|
$projects = mpull($projects, null, 'getPHID');
|
|
|
|
|
|
|
|
// The board layout engine needs to know about every object that it's
|
|
|
|
// going to be asked to do layout for. For now, we're just doing layout
|
|
|
|
// on every object on the boards. In the future, we could do layout on a
|
|
|
|
// smaller set of objects by using the constraints on this Query. For
|
|
|
|
// example, if the caller is only asking for open tasks, we only need
|
|
|
|
// to do layout on open tasks.
|
|
|
|
|
|
|
|
// This fetches too many objects (every type of object tagged with the
|
|
|
|
// project, not just tasks). We could narrow it by querying the edge
|
|
|
|
// table on the Maniphest side, but there's currently no way to build
|
|
|
|
// that query with EdgeQuery.
|
|
|
|
$edge_query = id(new PhabricatorEdgeQuery())
|
|
|
|
->withSourcePHIDs(array_keys($projects))
|
|
|
|
->withEdgeTypes(
|
|
|
|
array(
|
|
|
|
PhabricatorProjectProjectHasObjectEdgeType::EDGECONST,
|
|
|
|
));
|
|
|
|
|
|
|
|
$edge_query->execute();
|
|
|
|
$all_phids = $edge_query->getDestinationPHIDs();
|
|
|
|
|
|
|
|
// Since we overfetched PHIDs, filter out any non-tasks we got back.
|
|
|
|
foreach ($all_phids as $key => $phid) {
|
|
|
|
if (phid_get_type($phid) !== ManiphestTaskPHIDType::TYPECONST) {
|
|
|
|
unset($all_phids[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there are no tasks on the relevant boards, this query can't
|
|
|
|
// possibly hit anything so we're all done.
|
|
|
|
$task_phids = array_fuse($all_phids);
|
|
|
|
if (!$task_phids) {
|
|
|
|
throw new PhabricatorEmptyQueryException();
|
|
|
|
}
|
|
|
|
|
|
|
|
// We know everything we need to know, so perform board layout.
|
|
|
|
$engine = id(new PhabricatorBoardLayoutEngine())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->setFetchAllBoards(true)
|
|
|
|
->setBoardPHIDs(array_keys($projects))
|
|
|
|
->setObjectPHIDs($task_phids)
|
|
|
|
->executeLayout();
|
|
|
|
|
|
|
|
// Find the tasks that are in the constraint columns after board layout
|
|
|
|
// completes.
|
|
|
|
$select_phids = array();
|
|
|
|
foreach ($columns as $column) {
|
|
|
|
$in_column = $engine->getColumnObjectPHIDs(
|
|
|
|
$column->getProjectPHID(),
|
|
|
|
$column->getPHID());
|
|
|
|
foreach ($in_column as $phid) {
|
|
|
|
$select_phids[$phid] = $phid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$select_phids) {
|
|
|
|
throw new PhabricatorEmptyQueryException();
|
|
|
|
}
|
|
|
|
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.phid IN (%Ls)',
|
|
|
|
$select_phids);
|
|
|
|
}
|
|
|
|
|
2015-06-22 11:54:51 -07:00
|
|
|
return $where;
|
2012-11-01 10:47:45 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private function buildStatusWhereClause(AphrontDatabaseConnection $conn) {
|
2012-06-29 09:17:19 -07: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-29 16:16:33 -07:00
|
|
|
switch ($this->status) {
|
|
|
|
case self::STATUS_ANY:
|
|
|
|
return null;
|
|
|
|
case self::STATUS_OPEN:
|
2014-03-25 13:47:42 -07:00
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
2015-03-02 06:43:52 -08:00
|
|
|
'task.status IN (%Ls)',
|
2014-03-25 13:47:42 -07:00
|
|
|
ManiphestTaskStatus::getOpenStatusConstants());
|
2011-06-29 16:16:33 -07:00
|
|
|
case self::STATUS_CLOSED:
|
2014-03-25 13:47:42 -07:00
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
2015-03-02 06:43:52 -08:00
|
|
|
'task.status IN (%Ls)',
|
2014-03-25 13:47:42 -07:00
|
|
|
ManiphestTaskStatus::getClosedStatusConstants());
|
2011-06-29 16:16:33 -07:00
|
|
|
default:
|
2012-06-29 09:17:19 -07:00
|
|
|
$constant = idx($map, $this->status);
|
|
|
|
if (!$constant) {
|
2015-05-22 17:27:56 +10:00
|
|
|
throw new Exception(pht("Unknown status query '%s'!", $this->status));
|
2012-06-29 09:17:19 -07:00
|
|
|
}
|
|
|
|
return qsprintf(
|
|
|
|
$conn,
|
2015-03-02 06:43:52 -08:00
|
|
|
'task.status = %s',
|
2012-06-29 09:17:19 -07:00
|
|
|
$constant);
|
2011-06-29 16:16:33 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-01 10:47:45 -07:00
|
|
|
private function buildOwnerWhereClause(AphrontDatabaseConnection $conn) {
|
2015-04-23 15:44:27 -07:00
|
|
|
$subclause = array();
|
|
|
|
|
|
|
|
if ($this->noOwner) {
|
|
|
|
$subclause[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'task.ownerPHID IS NULL');
|
2011-06-29 16:16:33 -07:00
|
|
|
}
|
|
|
|
|
2015-04-23 15:44:27 -07:00
|
|
|
if ($this->anyOwner) {
|
|
|
|
$subclause[] = qsprintf(
|
2011-06-29 16:16:33 -07:00
|
|
|
$conn,
|
2015-04-23 15:44:27 -07:00
|
|
|
'task.ownerPHID IS NOT NULL');
|
|
|
|
}
|
|
|
|
|
2018-04-30 12:10:37 -07:00
|
|
|
if ($this->ownerPHIDs !== null) {
|
2015-04-23 15:44:27 -07:00
|
|
|
$subclause[] = qsprintf(
|
2011-06-29 16:16:33 -07:00
|
|
|
$conn,
|
2015-03-02 06:43:52 -08:00
|
|
|
'task.ownerPHID IN (%Ls)',
|
2011-06-29 16:16:33 -07:00
|
|
|
$this->ownerPHIDs);
|
|
|
|
}
|
2015-04-23 15:44:27 -07:00
|
|
|
|
|
|
|
if (!$subclause) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
return '('.implode(') OR (', $subclause).')';
|
2011-06-29 16:16:33 -07:00
|
|
|
}
|
|
|
|
|
2016-07-28 09:18:47 -07:00
|
|
|
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
|
|
|
|
$open_statuses = ManiphestTaskStatus::getOpenStatusConstants();
|
|
|
|
$edge_table = PhabricatorEdgeConfig::TABLE_NAME_EDGE;
|
|
|
|
$task_table = $this->newResultObject()->getTableName();
|
2015-01-12 13:42:37 -08:00
|
|
|
|
2016-07-28 10:47:40 -07:00
|
|
|
$parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
|
|
|
|
$subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
|
|
|
|
|
2016-07-28 09:18:47 -07:00
|
|
|
$joins = array();
|
|
|
|
if ($this->hasOpenParents !== null) {
|
|
|
|
if ($this->hasOpenParents) {
|
|
|
|
$join_type = 'JOIN';
|
|
|
|
} else {
|
|
|
|
$join_type = 'LEFT JOIN';
|
|
|
|
}
|
2015-01-12 13:42:37 -08:00
|
|
|
|
2016-07-28 09:18:47 -07:00
|
|
|
$joins[] = qsprintf(
|
2015-01-12 13:42:37 -08:00
|
|
|
$conn,
|
2016-07-28 09:18:47 -07:00
|
|
|
'%Q %T e_parent
|
|
|
|
ON e_parent.src = task.phid
|
|
|
|
AND e_parent.type = %d
|
|
|
|
%Q %T parent
|
|
|
|
ON e_parent.dst = parent.phid
|
|
|
|
AND parent.status IN (%Ls)',
|
|
|
|
$join_type,
|
|
|
|
$edge_table,
|
|
|
|
$parent_type,
|
|
|
|
$join_type,
|
|
|
|
$task_table,
|
|
|
|
$open_statuses);
|
2015-01-12 13:42:37 -08:00
|
|
|
}
|
|
|
|
|
2016-07-28 09:18:47 -07:00
|
|
|
if ($this->hasOpenSubtasks !== null) {
|
|
|
|
if ($this->hasOpenSubtasks) {
|
|
|
|
$join_type = 'JOIN';
|
|
|
|
} else {
|
|
|
|
$join_type = 'LEFT JOIN';
|
|
|
|
}
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
|
2015-01-12 13:42:37 -08:00
|
|
|
$joins[] = qsprintf(
|
2016-07-28 09:18:47 -07:00
|
|
|
$conn,
|
|
|
|
'%Q %T e_subtask
|
|
|
|
ON e_subtask.src = task.phid
|
|
|
|
AND e_subtask.type = %d
|
|
|
|
%Q %T subtask
|
|
|
|
ON e_subtask.dst = subtask.phid
|
|
|
|
AND subtask.status IN (%Ls)',
|
|
|
|
$join_type,
|
2015-01-12 13:42:37 -08:00
|
|
|
$edge_table,
|
2016-07-28 09:18:47 -07:00
|
|
|
$subtask_type,
|
|
|
|
$join_type,
|
|
|
|
$task_table,
|
|
|
|
$open_statuses);
|
2015-01-12 13:42:37 -08:00
|
|
|
}
|
|
|
|
|
2015-06-22 11:54:51 -07:00
|
|
|
if ($this->subscriberPHIDs !== null) {
|
2013-09-12 13:08:25 -07:00
|
|
|
$joins[] = qsprintf(
|
2016-07-28 09:18:47 -07:00
|
|
|
$conn,
|
2014-12-10 16:27:30 -08:00
|
|
|
'JOIN %T e_ccs ON e_ccs.src = task.phid '.
|
|
|
|
'AND e_ccs.type = %s '.
|
|
|
|
'AND e_ccs.dst in (%Ls)',
|
|
|
|
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
|
2015-01-03 10:33:25 +11:00
|
|
|
PhabricatorObjectHasSubscriberEdgeType::EDGECONST,
|
2014-12-10 16:27:30 -08:00
|
|
|
$this->subscriberPHIDs);
|
2013-09-12 13:08:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
switch ($this->groupBy) {
|
|
|
|
case self::GROUP_PROJECT:
|
|
|
|
$ignore_group_phids = $this->getIgnoreGroupedProjectPHIDs();
|
|
|
|
if ($ignore_group_phids) {
|
|
|
|
$joins[] = qsprintf(
|
2016-07-28 09:18:47 -07:00
|
|
|
$conn,
|
2014-07-17 15:42:30 -07:00
|
|
|
'LEFT JOIN %T projectGroup ON task.phid = projectGroup.src
|
|
|
|
AND projectGroup.type = %d
|
|
|
|
AND projectGroup.dst NOT IN (%Ls)',
|
|
|
|
$edge_table,
|
|
|
|
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
2013-09-12 13:08:25 -07:00
|
|
|
$ignore_group_phids);
|
|
|
|
} else {
|
|
|
|
$joins[] = qsprintf(
|
2016-07-28 09:18:47 -07:00
|
|
|
$conn,
|
2014-07-17 15:42:30 -07:00
|
|
|
'LEFT JOIN %T projectGroup ON task.phid = projectGroup.src
|
|
|
|
AND projectGroup.type = %d',
|
|
|
|
$edge_table,
|
|
|
|
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
}
|
2013-09-12 13:08:25 -07:00
|
|
|
$joins[] = qsprintf(
|
2016-07-28 09:18:47 -07:00
|
|
|
$conn,
|
2013-09-12 13:08:25 -07:00
|
|
|
'LEFT JOIN %T projectGroupName
|
2014-07-17 15:42:30 -07:00
|
|
|
ON projectGroup.dst = projectGroupName.indexedObjectPHID',
|
2013-09-12 13:08:25 -07:00
|
|
|
id(new ManiphestNameIndex())->getTableName());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-07-28 10:47:40 -07:00
|
|
|
if ($this->parentTaskIDs !== null) {
|
|
|
|
$joins[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'JOIN %T e_has_parent
|
|
|
|
ON e_has_parent.src = task.phid
|
|
|
|
AND e_has_parent.type = %d
|
|
|
|
JOIN %T has_parent
|
|
|
|
ON e_has_parent.dst = has_parent.phid
|
|
|
|
AND has_parent.id IN (%Ld)',
|
|
|
|
$edge_table,
|
|
|
|
$parent_type,
|
|
|
|
$task_table,
|
|
|
|
$this->parentTaskIDs);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->subtaskIDs !== null) {
|
|
|
|
$joins[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'JOIN %T e_has_subtask
|
|
|
|
ON e_has_subtask.src = task.phid
|
|
|
|
AND e_has_subtask.type = %d
|
|
|
|
JOIN %T has_subtask
|
|
|
|
ON e_has_subtask.dst = has_subtask.phid
|
|
|
|
AND has_subtask.id IN (%Ld)',
|
|
|
|
$edge_table,
|
|
|
|
$subtask_type,
|
|
|
|
$task_table,
|
|
|
|
$this->subtaskIDs);
|
|
|
|
}
|
|
|
|
|
2016-07-28 09:18:47 -07:00
|
|
|
$joins[] = parent::buildJoinClauseParts($conn);
|
2013-09-16 16:03:09 -07:00
|
|
|
|
2015-04-23 03:58:04 -07:00
|
|
|
return $joins;
|
2013-09-12 13:08:25 -07:00
|
|
|
}
|
|
|
|
|
2015-04-18 07:53:43 -07:00
|
|
|
protected function buildGroupClause(AphrontDatabaseConnection $conn_r) {
|
2016-07-28 09:18:47 -07:00
|
|
|
$joined_multiple_rows =
|
|
|
|
($this->hasOpenParents !== null) ||
|
|
|
|
($this->hasOpenSubtasks !== null) ||
|
2016-07-28 10:47:40 -07:00
|
|
|
($this->parentTaskIDs !== null) ||
|
|
|
|
($this->subtaskIDs !== null) ||
|
2016-07-28 09:18:47 -07:00
|
|
|
$this->shouldGroupQueryResultRows();
|
2013-09-12 13:08:25 -07:00
|
|
|
|
|
|
|
$joined_project_name = ($this->groupBy == self::GROUP_PROJECT);
|
|
|
|
|
|
|
|
// If we're joining multiple rows, we need to group the results by the
|
|
|
|
// task IDs.
|
2013-09-16 16:03:09 -07:00
|
|
|
if ($joined_multiple_rows) {
|
2013-09-12 13:08:25 -07:00
|
|
|
if ($joined_project_name) {
|
2014-07-17 15:42:30 -07:00
|
|
|
return 'GROUP BY task.phid, projectGroup.dst';
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
} else {
|
2013-09-16 16:03:09 -07:00
|
|
|
return 'GROUP BY task.phid';
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
}
|
2013-09-12 13:08:25 -07:00
|
|
|
} else {
|
|
|
|
return '';
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
}
|
2013-09-12 13:08:25 -07:00
|
|
|
}
|
|
|
|
|
2016-07-28 09:18:47 -07:00
|
|
|
|
|
|
|
protected function buildHavingClauseParts(AphrontDatabaseConnection $conn) {
|
|
|
|
$having = parent::buildHavingClauseParts($conn);
|
|
|
|
|
|
|
|
if ($this->hasOpenParents !== null) {
|
|
|
|
if (!$this->hasOpenParents) {
|
|
|
|
$having[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'COUNT(parent.phid) = 0');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->hasOpenSubtasks !== null) {
|
|
|
|
if (!$this->hasOpenSubtasks) {
|
|
|
|
$having[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'COUNT(subtask.phid) = 0');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $having;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-09-12 13:08:25 -07:00
|
|
|
/**
|
|
|
|
* Return project PHIDs which we should ignore when grouping tasks by
|
|
|
|
* project. For example, if a user issues a query like:
|
|
|
|
*
|
2016-01-24 09:39:53 -08:00
|
|
|
* Tasks tagged with all projects: Frontend, Bugs
|
2013-09-12 13:08:25 -07:00
|
|
|
*
|
|
|
|
* ...then we don't show "Frontend" or "Bugs" groups in the result set, since
|
|
|
|
* they're meaningless as all results are in both groups.
|
|
|
|
*
|
|
|
|
* Similarly, for queries like:
|
|
|
|
*
|
2016-01-24 09:39:53 -08:00
|
|
|
* Tasks tagged with any projects: Public Relations
|
2013-09-12 13:08:25 -07:00
|
|
|
*
|
|
|
|
* ...we ignore the single project, as every result is in that project. (In
|
|
|
|
* the case that there are several "any" projects, we do not ignore them.)
|
|
|
|
*
|
|
|
|
* @return list<phid> Project PHIDs which should be ignored in query
|
|
|
|
* construction.
|
|
|
|
*/
|
|
|
|
private function getIgnoreGroupedProjectPHIDs() {
|
2015-04-23 04:10:39 -07:00
|
|
|
// Maybe we should also exclude the "OPERATOR_NOT" PHIDs? It won't
|
|
|
|
// impact the results, but we might end up with a better query plan.
|
|
|
|
// Investigate this on real data? This is likely very rare.
|
|
|
|
|
|
|
|
$edge_types = array(
|
|
|
|
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
|
|
|
);
|
|
|
|
|
2013-09-12 13:08:25 -07:00
|
|
|
$phids = 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-19 19:47:34 -07:00
|
|
|
|
2015-04-23 04:10:39 -07:00
|
|
|
$phids[] = $this->getEdgeLogicValues(
|
|
|
|
$edge_types,
|
|
|
|
array(
|
|
|
|
PhabricatorQueryConstraint::OPERATOR_AND,
|
|
|
|
));
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
|
2015-04-23 04:10:39 -07:00
|
|
|
$any = $this->getEdgeLogicValues(
|
|
|
|
$edge_types,
|
|
|
|
array(
|
|
|
|
PhabricatorQueryConstraint::OPERATOR_OR,
|
|
|
|
));
|
|
|
|
if (count($any) == 1) {
|
|
|
|
$phids[] = $any;
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
}
|
|
|
|
|
2013-09-12 13:08:25 -07:00
|
|
|
return array_mergev($phids);
|
Add "Group by: Project" to Maniphest
Summary:
Allow tasks to be grouped by project. Since this is many-to-many and we're a little deficient on indexes for doing this on the database, we pull all matching tasks and group them in PHP. This shouldn't be a huge issue for any existing installs, though, and we can add keys when we run into one.
- When a task is in multiple projects, it appears under multiple headers.
- When a query has a task filter, those projects are omitted from the grouping (they'd always show everything, which isn't useful). Notably, if you search for "Differential", you can now see "Bugs", "Feature Requests", etc.
Test Plan: Selected "Group by: Project".
Reviewers: btrahan, Josereyes
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T923
Differential Revision: https://secure.phabricator.com/D1953
2012-03-19 19:47:34 -07:00
|
|
|
}
|
|
|
|
|
2015-04-12 16:44:15 -07:00
|
|
|
protected function getResultCursor($result) {
|
2013-09-13 07:13:06 -07:00
|
|
|
$id = $result->getID();
|
|
|
|
|
2015-04-12 16:44:15 -07:00
|
|
|
if ($this->groupBy == self::GROUP_PROJECT) {
|
2015-05-14 06:50:28 +10:00
|
|
|
return rtrim($id.'.'.$result->getGroupByProjectPHID(), '.');
|
2013-09-13 07:13:06 -07:00
|
|
|
}
|
2015-04-12 16:44:15 -07:00
|
|
|
|
|
|
|
return $id;
|
2013-09-13 07:13:06 -07:00
|
|
|
}
|
|
|
|
|
2015-06-08 12:23:13 -07:00
|
|
|
public function getBuiltinOrders() {
|
|
|
|
$orders = array(
|
|
|
|
'priority' => array(
|
|
|
|
'vector' => array('priority', 'subpriority', 'id'),
|
|
|
|
'name' => pht('Priority'),
|
|
|
|
'aliases' => array(self::ORDER_PRIORITY),
|
|
|
|
),
|
|
|
|
'updated' => array(
|
|
|
|
'vector' => array('updated', 'id'),
|
2015-06-10 12:21:08 -07:00
|
|
|
'name' => pht('Date Updated (Latest First)'),
|
2015-06-08 12:23:13 -07:00
|
|
|
'aliases' => array(self::ORDER_MODIFIED),
|
|
|
|
),
|
2015-06-10 12:21:08 -07:00
|
|
|
'outdated' => array(
|
|
|
|
'vector' => array('-updated', '-id'),
|
|
|
|
'name' => pht('Date Updated (Oldest First)'),
|
2018-02-08 15:13:03 -08:00
|
|
|
),
|
|
|
|
'closed' => array(
|
|
|
|
'vector' => array('closed', 'id'),
|
|
|
|
'name' => pht('Date Closed (Latest First)'),
|
|
|
|
),
|
2015-06-08 12:23:13 -07:00
|
|
|
'title' => array(
|
|
|
|
'vector' => array('title', 'id'),
|
|
|
|
'name' => pht('Title'),
|
|
|
|
'aliases' => array(self::ORDER_TITLE),
|
|
|
|
),
|
|
|
|
) + parent::getBuiltinOrders();
|
|
|
|
|
|
|
|
// Alias the "newest" builtin to the historical key for it.
|
|
|
|
$orders['newest']['aliases'][] = self::ORDER_CREATED;
|
|
|
|
|
|
|
|
$orders = array_select_keys(
|
|
|
|
$orders,
|
|
|
|
array(
|
|
|
|
'priority',
|
|
|
|
'updated',
|
2015-06-10 12:21:08 -07:00
|
|
|
'outdated',
|
2015-06-08 12:23:13 -07:00
|
|
|
'newest',
|
|
|
|
'oldest',
|
2018-02-08 15:13:03 -08:00
|
|
|
'closed',
|
2015-06-08 12:23:13 -07:00
|
|
|
'title',
|
|
|
|
)) + $orders;
|
|
|
|
|
|
|
|
return $orders;
|
|
|
|
}
|
|
|
|
|
2015-04-12 13:01:21 -07:00
|
|
|
public function getOrderableColumns() {
|
|
|
|
return parent::getOrderableColumns() + array(
|
|
|
|
'priority' => array(
|
|
|
|
'table' => 'task',
|
|
|
|
'column' => 'priority',
|
|
|
|
'type' => 'int',
|
|
|
|
),
|
|
|
|
'owner' => array(
|
|
|
|
'table' => 'task',
|
|
|
|
'column' => 'ownerOrdering',
|
|
|
|
'null' => 'head',
|
|
|
|
'reverse' => true,
|
|
|
|
'type' => 'string',
|
|
|
|
),
|
|
|
|
'status' => array(
|
|
|
|
'table' => 'task',
|
|
|
|
'column' => 'status',
|
|
|
|
'type' => 'string',
|
|
|
|
'reverse' => true,
|
|
|
|
),
|
|
|
|
'project' => array(
|
|
|
|
'table' => 'projectGroupName',
|
|
|
|
'column' => 'indexedObjectName',
|
|
|
|
'type' => 'string',
|
|
|
|
'null' => 'head',
|
|
|
|
'reverse' => true,
|
|
|
|
),
|
|
|
|
'title' => array(
|
|
|
|
'table' => 'task',
|
|
|
|
'column' => 'title',
|
|
|
|
'type' => 'string',
|
|
|
|
'reverse' => true,
|
|
|
|
),
|
|
|
|
'subpriority' => array(
|
|
|
|
'table' => 'task',
|
|
|
|
'column' => 'subpriority',
|
|
|
|
'type' => 'float',
|
|
|
|
),
|
|
|
|
'updated' => array(
|
|
|
|
'table' => 'task',
|
|
|
|
'column' => 'dateModified',
|
|
|
|
'type' => 'int',
|
|
|
|
),
|
2018-02-08 15:13:03 -08:00
|
|
|
'closed' => array(
|
|
|
|
'table' => 'task',
|
|
|
|
'column' => 'closedEpoch',
|
|
|
|
'type' => 'int',
|
|
|
|
'null' => 'tail',
|
|
|
|
),
|
2015-04-12 13:01:21 -07:00
|
|
|
);
|
|
|
|
}
|
2013-09-13 07:13:06 -07:00
|
|
|
|
2015-04-12 13:01:21 -07:00
|
|
|
protected function getPagingValueMap($cursor, array $keys) {
|
|
|
|
$cursor_parts = explode('.', $cursor, 2);
|
2013-09-13 07:13:06 -07:00
|
|
|
$task_id = $cursor_parts[0];
|
|
|
|
$group_id = idx($cursor_parts, 1);
|
|
|
|
|
2015-04-12 13:01:21 -07:00
|
|
|
$task = $this->loadCursorObject($task_id);
|
2015-04-11 22:26:19 -07:00
|
|
|
|
2015-04-12 13:01:21 -07:00
|
|
|
$map = array(
|
|
|
|
'id' => $task->getID(),
|
|
|
|
'priority' => $task->getPriority(),
|
|
|
|
'subpriority' => $task->getSubpriority(),
|
|
|
|
'owner' => $task->getOwnerOrdering(),
|
|
|
|
'status' => $task->getStatus(),
|
|
|
|
'title' => $task->getTitle(),
|
|
|
|
'updated' => $task->getDateModified(),
|
2018-02-08 15:13:03 -08:00
|
|
|
'closed' => $task->getClosedEpoch(),
|
2015-04-12 13:01:21 -07:00
|
|
|
);
|
2013-09-13 07:13:06 -07:00
|
|
|
|
2015-04-12 13:01:21 -07:00
|
|
|
foreach ($keys as $key) {
|
|
|
|
switch ($key) {
|
|
|
|
case 'project':
|
|
|
|
$value = null;
|
|
|
|
if ($group_id) {
|
|
|
|
$paging_projects = id(new PhabricatorProjectQuery())
|
|
|
|
->setViewer($this->getViewer())
|
|
|
|
->withPHIDs(array($group_id))
|
|
|
|
->execute();
|
|
|
|
if ($paging_projects) {
|
|
|
|
$value = head($paging_projects)->getName();
|
|
|
|
}
|
2014-08-02 18:22:16 +10:00
|
|
|
}
|
2015-04-12 13:01:21 -07:00
|
|
|
$map[$key] = $value;
|
2014-08-02 18:22:16 +10:00
|
|
|
break;
|
|
|
|
}
|
2013-09-13 07:13:06 -07:00
|
|
|
}
|
|
|
|
|
2015-04-12 13:01:21 -07:00
|
|
|
foreach ($keys as $key) {
|
|
|
|
if ($this->isCustomFieldOrderKey($key)) {
|
|
|
|
$map += $this->getPagingValueMapForCustomFields($task);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2015-03-14 13:27:47 -07:00
|
|
|
|
2015-04-12 13:01:21 -07:00
|
|
|
return $map;
|
2013-09-13 07:13:06 -07:00
|
|
|
}
|
2013-09-12 13:08:25 -07:00
|
|
|
|
2015-04-11 19:00:53 -07:00
|
|
|
protected function getPrimaryTableAlias() {
|
|
|
|
return 'task';
|
2013-09-16 16:03:09 -07:00
|
|
|
}
|
|
|
|
|
Lock policy queries to their applications
Summary:
While we mostly have reasonable effective object accessibility when you lock a user out of an application, it's primarily enforced at the controller level. Users can still, e.g., load the handles of objects they can't actually see. Instead, lock the queries to the applications so that you can, e.g., never load a revision if you don't have access to Differential.
This has several parts:
- For PolicyAware queries, provide an application class name method.
- If the query specifies a class name and the user doesn't have permission to use it, fail the entire query unconditionally.
- For handles, simplify query construction and count all the PHIDs as "restricted" so we get a UI full of "restricted" instead of "unknown" handles.
Test Plan:
- Added a unit test to verify I got all the class names right.
- Browsed around, logged in/out as a normal user with public policies on and off.
- Browsed around, logged in/out as a restricted user with public policies on and off. With restrictions, saw all traces of restricted apps removed or restricted.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D7367
2013-10-21 17:20:27 -07:00
|
|
|
public function getQueryApplicationClass() {
|
2014-07-23 10:03:09 +10:00
|
|
|
return 'PhabricatorManiphestApplication';
|
Lock policy queries to their applications
Summary:
While we mostly have reasonable effective object accessibility when you lock a user out of an application, it's primarily enforced at the controller level. Users can still, e.g., load the handles of objects they can't actually see. Instead, lock the queries to the applications so that you can, e.g., never load a revision if you don't have access to Differential.
This has several parts:
- For PolicyAware queries, provide an application class name method.
- If the query specifies a class name and the user doesn't have permission to use it, fail the entire query unconditionally.
- For handles, simplify query construction and count all the PHIDs as "restricted" so we get a UI full of "restricted" instead of "unknown" handles.
Test Plan:
- Added a unit test to verify I got all the class names right.
- Browsed around, logged in/out as a normal user with public policies on and off.
- Browsed around, logged in/out as a restricted user with public policies on and off. With restrictions, saw all traces of restricted apps removed or restricted.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D7367
2013-10-21 17:20:27 -07:00
|
|
|
}
|
|
|
|
|
2011-06-29 16:16:33 -07:00
|
|
|
}
|