Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2890746
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
37 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php
index f01346af1e..8d2fe79735 100644
--- a/src/applications/maniphest/query/ManiphestTaskQuery.php
+++ b/src/applications/maniphest/query/ManiphestTaskQuery.php
@@ -1,852 +1,895 @@
<?php
/**
* Query tasks by specific criteria. This class uses the higher-performance
* but less-general Maniphest indexes to satisfy queries.
*/
final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $taskIDs;
private $taskPHIDs;
private $authorPHIDs;
private $ownerPHIDs;
private $noOwner;
private $anyOwner;
private $subscriberPHIDs;
private $dateCreatedAfter;
private $dateCreatedBefore;
private $dateModifiedAfter;
private $dateModifiedBefore;
private $subpriorityMin;
private $subpriorityMax;
private $bridgedObjectPHIDs;
private $hasOpenParents;
private $hasOpenSubtasks;
+ private $parentTaskIDs;
+ private $subtaskIDs;
private $fullTextSearch = '';
private $status = 'status-any';
const STATUS_ANY = 'status-any';
const STATUS_OPEN = 'status-open';
const STATUS_CLOSED = 'status-closed';
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';
private $statuses;
private $priorities;
private $subpriorities;
private $groupBy = 'group-none';
const GROUP_NONE = 'group-none';
const GROUP_PRIORITY = 'group-priority';
const GROUP_OWNER = 'group-owner';
const GROUP_STATUS = 'group-status';
const GROUP_PROJECT = 'group-project';
const ORDER_PRIORITY = 'order-priority';
const ORDER_CREATED = 'order-created';
const ORDER_MODIFIED = 'order-modified';
const ORDER_TITLE = 'order-title';
private $needSubscriberPHIDs;
private $needProjectPHIDs;
public function withAuthors(array $authors) {
$this->authorPHIDs = $authors;
return $this;
}
public function withIDs(array $ids) {
$this->taskIDs = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->taskPHIDs = $phids;
return $this;
}
public function withOwners(array $owners) {
$no_owner = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN;
$any_owner = PhabricatorPeopleAnyOwnerDatasource::FUNCTION_TOKEN;
foreach ($owners as $k => $phid) {
if ($phid === $no_owner || $phid === null) {
$this->noOwner = true;
unset($owners[$k]);
break;
}
if ($phid === $any_owner) {
$this->anyOwner = true;
unset($owners[$k]);
break;
}
}
$this->ownerPHIDs = $owners;
return $this;
}
public function withStatus($status) {
$this->status = $status;
return $this;
}
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withPriorities(array $priorities) {
$this->priorities = $priorities;
return $this;
}
public function withSubpriorities(array $subpriorities) {
$this->subpriorities = $subpriorities;
return $this;
}
public function withSubpriorityBetween($min, $max) {
$this->subpriorityMin = $min;
$this->subpriorityMax = $max;
return $this;
}
public function withSubscribers(array $subscribers) {
$this->subscriberPHIDs = $subscribers;
return $this;
}
public function withFullTextSearch($fulltext_search) {
$this->fullTextSearch = $fulltext_search;
return $this;
}
public function setGroupBy($group) {
$this->groupBy = $group;
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);
return $this;
}
public function withOpenSubtasks($value) {
$this->hasOpenSubtasks = $value;
return $this;
}
public function withOpenParents($value) {
$this->hasOpenParents = $value;
return $this;
}
+ public function withParentTaskIDs(array $ids) {
+ $this->parentTaskIDs = $ids;
+ return $this;
+ }
+
+ public function withSubtaskIDs(array $ids) {
+ $this->subtaskIDs = $ids;
+ return $this;
+ }
+
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;
}
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;
}
public function needSubscriberPHIDs($bool) {
$this->needSubscriberPHIDs = $bool;
return $this;
}
public function needProjectPHIDs($bool) {
$this->needProjectPHIDs = $bool;
return $this;
}
public function withBridgedObjectPHIDs(array $phids) {
$this->bridgedObjectPHIDs = $phids;
return $this;
}
public function newResultObject() {
return new ManiphestTask();
}
protected function loadPage() {
$task_dao = new ManiphestTask();
$conn = $task_dao->establishConnection('r');
$where = $this->buildWhereClause($conn);
$group_column = '';
switch ($this->groupBy) {
case self::GROUP_PROJECT:
$group_column = qsprintf(
$conn,
', projectGroupName.indexedObjectPHID projectGroupPHID');
break;
}
$rows = queryfx_all(
$conn,
'%Q %Q FROM %T task %Q %Q %Q %Q %Q %Q',
$this->buildSelectClause($conn),
$group_column,
$task_dao->getTableName(),
$this->buildJoinClause($conn),
$where,
$this->buildGroupClause($conn),
$this->buildHavingClause($conn),
$this->buildOrderClause($conn),
$this->buildLimitClause($conn));
switch ($this->groupBy) {
case self::GROUP_PROJECT:
$data = ipull($rows, null, 'id');
break;
default:
$data = $rows;
break;
}
$tasks = $task_dao->loadAllFromArray($data);
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;
}
return $tasks;
}
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) {
if (!$task->getGroupByProjectPHID()) {
// 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.
continue;
}
if (empty($projects[$task->getGroupByProjectPHID()])) {
unset($tasks[$key]);
}
}
}
return $tasks;
}
protected function didFilterPage(array $tasks) {
$phids = mpull($tasks, 'getPHID');
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);
}
}
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);
}
}
return $tasks;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
$where[] = $this->buildStatusWhereClause($conn);
$where[] = $this->buildOwnerWhereClause($conn);
$where[] = $this->buildFullTextWhereClause($conn);
if ($this->taskIDs !== null) {
$where[] = qsprintf(
$conn,
'task.id in (%Ld)',
$this->taskIDs);
}
if ($this->taskPHIDs !== null) {
$where[] = qsprintf(
$conn,
'task.phid in (%Ls)',
$this->taskPHIDs);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'task.status IN (%Ls)',
$this->statuses);
}
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);
}
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);
}
if ($this->subpriorityMin !== null) {
$where[] = qsprintf(
$conn,
'task.subpriority >= %f',
$this->subpriorityMin);
}
if ($this->subpriorityMax !== null) {
$where[] = qsprintf(
$conn,
'task.subpriority <= %f',
$this->subpriorityMax);
}
if ($this->bridgedObjectPHIDs !== null) {
$where[] = qsprintf(
$conn,
'task.bridgedObjectPHID IN (%Ls)',
$this->bridgedObjectPHIDs);
}
return $where;
}
private function buildStatusWhereClause(AphrontDatabaseConnection $conn) {
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,
);
switch ($this->status) {
case self::STATUS_ANY:
return null;
case self::STATUS_OPEN:
return qsprintf(
$conn,
'task.status IN (%Ls)',
ManiphestTaskStatus::getOpenStatusConstants());
case self::STATUS_CLOSED:
return qsprintf(
$conn,
'task.status IN (%Ls)',
ManiphestTaskStatus::getClosedStatusConstants());
default:
$constant = idx($map, $this->status);
if (!$constant) {
throw new Exception(pht("Unknown status query '%s'!", $this->status));
}
return qsprintf(
$conn,
'task.status = %s',
$constant);
}
}
private function buildOwnerWhereClause(AphrontDatabaseConnection $conn) {
$subclause = array();
if ($this->noOwner) {
$subclause[] = qsprintf(
$conn,
'task.ownerPHID IS NULL');
}
if ($this->anyOwner) {
$subclause[] = qsprintf(
$conn,
'task.ownerPHID IS NOT NULL');
}
if ($this->ownerPHIDs) {
$subclause[] = qsprintf(
$conn,
'task.ownerPHID IN (%Ls)',
$this->ownerPHIDs);
}
if (!$subclause) {
return '';
}
return '('.implode(') OR (', $subclause).')';
}
private function buildFullTextWhereClause(AphrontDatabaseConnection $conn) {
if (!strlen($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 = id(new PhabricatorSavedQuery())
->setEngineClassName('PhabricatorSearchApplicationSearchEngine')
->setParameter('query', $this->fullTextSearch);
// NOTE: Setting this to something larger than 2^53 will raise errors in
// ElasticSearch, and billions of results won't fit in memory anyway.
$fulltext_query->setParameter('limit', 100000);
$fulltext_query->setParameter('types',
array(ManiphestTaskPHIDType::TYPECONST));
$engine = PhabricatorFulltextStorageEngine::loadEngine();
$fulltext_results = $engine->executeSearch($fulltext_query);
if (empty($fulltext_results)) {
$fulltext_results = array(null);
}
return qsprintf(
$conn,
'task.phid IN (%Ls)',
$fulltext_results);
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$open_statuses = ManiphestTaskStatus::getOpenStatusConstants();
$edge_table = PhabricatorEdgeConfig::TABLE_NAME_EDGE;
$task_table = $this->newResultObject()->getTableName();
+ $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
+ $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
+
$joins = array();
if ($this->hasOpenParents !== null) {
- $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
-
if ($this->hasOpenParents) {
$join_type = 'JOIN';
} else {
$join_type = 'LEFT JOIN';
}
$joins[] = qsprintf(
$conn,
'%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);
}
if ($this->hasOpenSubtasks !== null) {
- $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
-
if ($this->hasOpenSubtasks) {
$join_type = 'JOIN';
} else {
$join_type = 'LEFT JOIN';
}
$joins[] = qsprintf(
$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,
$edge_table,
$subtask_type,
$join_type,
$task_table,
$open_statuses);
}
if ($this->subscriberPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'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,
PhabricatorObjectHasSubscriberEdgeType::EDGECONST,
$this->subscriberPHIDs);
}
switch ($this->groupBy) {
case self::GROUP_PROJECT:
$ignore_group_phids = $this->getIgnoreGroupedProjectPHIDs();
if ($ignore_group_phids) {
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T projectGroup ON task.phid = projectGroup.src
AND projectGroup.type = %d
AND projectGroup.dst NOT IN (%Ls)',
$edge_table,
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
$ignore_group_phids);
} else {
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T projectGroup ON task.phid = projectGroup.src
AND projectGroup.type = %d',
$edge_table,
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
}
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T projectGroupName
ON projectGroup.dst = projectGroupName.indexedObjectPHID',
id(new ManiphestNameIndex())->getTableName());
break;
}
+ 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);
+ }
+
$joins[] = parent::buildJoinClauseParts($conn);
return $joins;
}
protected function buildGroupClause(AphrontDatabaseConnection $conn_r) {
$joined_multiple_rows =
($this->hasOpenParents !== null) ||
($this->hasOpenSubtasks !== null) ||
+ ($this->parentTaskIDs !== null) ||
+ ($this->subtaskIDs !== null) ||
$this->shouldGroupQueryResultRows();
$joined_project_name = ($this->groupBy == self::GROUP_PROJECT);
// If we're joining multiple rows, we need to group the results by the
// task IDs.
if ($joined_multiple_rows) {
if ($joined_project_name) {
return 'GROUP BY task.phid, projectGroup.dst';
} else {
return 'GROUP BY task.phid';
}
} else {
return '';
}
}
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;
}
/**
* Return project PHIDs which we should ignore when grouping tasks by
* project. For example, if a user issues a query like:
*
* Tasks tagged with all projects: Frontend, Bugs
*
* ...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:
*
* Tasks tagged with any projects: Public Relations
*
* ...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() {
// 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,
);
$phids = array();
$phids[] = $this->getEdgeLogicValues(
$edge_types,
array(
PhabricatorQueryConstraint::OPERATOR_AND,
));
$any = $this->getEdgeLogicValues(
$edge_types,
array(
PhabricatorQueryConstraint::OPERATOR_OR,
));
if (count($any) == 1) {
$phids[] = $any;
}
return array_mergev($phids);
}
protected function getResultCursor($result) {
$id = $result->getID();
if ($this->groupBy == self::GROUP_PROJECT) {
return rtrim($id.'.'.$result->getGroupByProjectPHID(), '.');
}
return $id;
}
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'),
'name' => pht('Date Updated (Latest First)'),
'aliases' => array(self::ORDER_MODIFIED),
),
'outdated' => array(
'vector' => array('-updated', '-id'),
'name' => pht('Date Updated (Oldest First)'),
),
'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',
'outdated',
'newest',
'oldest',
'title',
)) + $orders;
return $orders;
}
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',
),
);
}
protected function getPagingValueMap($cursor, array $keys) {
$cursor_parts = explode('.', $cursor, 2);
$task_id = $cursor_parts[0];
$group_id = idx($cursor_parts, 1);
$task = $this->loadCursorObject($task_id);
$map = array(
'id' => $task->getID(),
'priority' => $task->getPriority(),
'subpriority' => $task->getSubpriority(),
'owner' => $task->getOwnerOrdering(),
'status' => $task->getStatus(),
'title' => $task->getTitle(),
'updated' => $task->getDateModified(),
);
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();
}
}
$map[$key] = $value;
break;
}
}
foreach ($keys as $key) {
if ($this->isCustomFieldOrderKey($key)) {
$map += $this->getPagingValueMapForCustomFields($task);
break;
}
}
return $map;
}
protected function getPrimaryTableAlias() {
return 'task';
}
public function getQueryApplicationClass() {
return 'PhabricatorManiphestApplication';
}
}
diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php
index 63d1688f5f..0022940542 100644
--- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php
+++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php
@@ -1,409 +1,427 @@
<?php
final class ManiphestTaskSearchEngine
extends PhabricatorApplicationSearchEngine {
private $showBatchControls;
private $baseURI;
private $isBoardView;
public function setIsBoardView($is_board_view) {
$this->isBoardView = $is_board_view;
return $this;
}
public function getIsBoardView() {
return $this->isBoardView;
}
public function setBaseURI($base_uri) {
$this->baseURI = $base_uri;
return $this;
}
public function getBaseURI() {
return $this->baseURI;
}
public function setShowBatchControls($show_batch_controls) {
$this->showBatchControls = $show_batch_controls;
return $this;
}
public function getResultTypeDescription() {
return pht('Tasks');
}
public function getApplicationClassName() {
return 'PhabricatorManiphestApplication';
}
public function newQuery() {
return id(new ManiphestTaskQuery())
->needProjectPHIDs(true);
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorOwnersSearchField())
->setLabel(pht('Assigned To'))
->setKey('assignedPHIDs')
->setConduitKey('assigned')
->setAliases(array('assigned'))
->setDescription(
pht('Search for tasks owned by a user from a list.')),
id(new PhabricatorUsersSearchField())
->setLabel(pht('Authors'))
->setKey('authorPHIDs')
->setAliases(array('author', 'authors'))
->setDescription(
pht('Search for tasks with given authors.')),
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Statuses'))
->setKey('statuses')
->setAliases(array('status'))
->setDescription(
pht('Search for tasks with given statuses.'))
->setDatasource(new ManiphestTaskStatusFunctionDatasource()),
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Priorities'))
->setKey('priorities')
->setAliases(array('priority'))
->setDescription(
pht('Search for tasks with given priorities.'))
->setConduitParameterType(new ConduitIntListParameterType())
->setDatasource(new ManiphestTaskPriorityDatasource()),
id(new PhabricatorSearchTextField())
->setLabel(pht('Contains Words'))
->setKey('fulltext'),
id(new PhabricatorSearchThreeStateField())
->setLabel(pht('Open Parents'))
->setKey('hasParents')
->setAliases(array('blocking'))
->setOptions(
pht('(Show All)'),
pht('Show Only Tasks With Open Parents'),
pht('Show Only Tasks Without Open Parents')),
id(new PhabricatorSearchThreeStateField())
->setLabel(pht('Open Subtasks'))
->setKey('hasSubtasks')
->setAliases(array('blocked'))
->setOptions(
pht('(Show All)'),
pht('Show Only Tasks With Open Subtasks'),
pht('Show Only Tasks Without Open Subtasks')),
+ id(new PhabricatorIDsSearchField())
+ ->setLabel(pht('Parent IDs'))
+ ->setKey('parentIDs')
+ ->setAliases(array('parentID')),
+ id(new PhabricatorIDsSearchField())
+ ->setLabel(pht('Subtask IDs'))
+ ->setKey('subtaskIDs')
+ ->setAliases(array('subtaskID')),
id(new PhabricatorSearchSelectField())
->setLabel(pht('Group By'))
->setKey('group')
->setOptions($this->getGroupOptions()),
id(new PhabricatorSearchDateField())
->setLabel(pht('Created After'))
->setKey('createdStart'),
id(new PhabricatorSearchDateField())
->setLabel(pht('Created Before'))
->setKey('createdEnd'),
id(new PhabricatorSearchDateField())
->setLabel(pht('Updated After'))
->setKey('modifiedStart'),
id(new PhabricatorSearchDateField())
->setLabel(pht('Updated Before'))
->setKey('modifiedEnd'),
id(new PhabricatorSearchTextField())
->setLabel(pht('Page Size'))
->setKey('limit'),
);
}
protected function getDefaultFieldOrder() {
return array(
'assignedPHIDs',
'projectPHIDs',
'authorPHIDs',
'subscriberPHIDs',
'statuses',
'priorities',
'fulltext',
'hasParents',
'hasSubtasks',
+ 'parentIDs',
+ 'subtaskIDs',
'group',
'order',
'ids',
'...',
'createdStart',
'createdEnd',
'modifiedStart',
'modifiedEnd',
'limit',
);
}
protected function getHiddenFields() {
$keys = array();
if ($this->getIsBoardView()) {
$keys[] = 'group';
$keys[] = 'order';
$keys[] = 'limit';
}
return $keys;
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['assignedPHIDs']) {
$query->withOwners($map['assignedPHIDs']);
}
if ($map['authorPHIDs']) {
$query->withAuthors($map['authorPHIDs']);
}
if ($map['statuses']) {
$query->withStatuses($map['statuses']);
}
if ($map['priorities']) {
$query->withPriorities($map['priorities']);
}
if ($map['createdStart']) {
$query->withDateCreatedAfter($map['createdStart']);
}
if ($map['createdEnd']) {
$query->withDateCreatedBefore($map['createdEnd']);
}
if ($map['modifiedStart']) {
$query->withDateModifiedAfter($map['modifiedStart']);
}
if ($map['modifiedEnd']) {
$query->withDateModifiedBefore($map['modifiedEnd']);
}
if ($map['hasParents'] !== null) {
$query->withOpenParents($map['hasParents']);
}
if ($map['hasSubtasks'] !== null) {
$query->withOpenSubtasks($map['hasSubtasks']);
}
if (strlen($map['fulltext'])) {
$query->withFullTextSearch($map['fulltext']);
}
+ if ($map['parentIDs']) {
+ $query->withParentTaskIDs($map['parentIDs']);
+ }
+
+ if ($map['subtaskIDs']) {
+ $query->withSubtaskIDs($map['subtaskIDs']);
+ }
+
$group = idx($map, 'group');
$group = idx($this->getGroupValues(), $group);
if ($group) {
$query->setGroupBy($group);
} else {
$query->setGroupBy(head($this->getGroupValues()));
}
if ($map['ids']) {
$ids = $map['ids'];
foreach ($ids as $key => $id) {
$id = trim($id, ' Tt');
if (!$id || !is_numeric($id)) {
unset($ids[$key]);
} else {
$ids[$key] = $id;
}
}
if ($ids) {
$query->withIDs($ids);
}
}
return $query;
}
protected function getURI($path) {
if ($this->baseURI) {
return $this->baseURI.$path;
}
return '/maniphest/'.$path;
}
protected function getBuiltinQueryNames() {
$names = array();
if ($this->requireViewer()->isLoggedIn()) {
$names['assigned'] = pht('Assigned');
$names['authored'] = pht('Authored');
$names['subscribed'] = pht('Subscribed');
}
$names['open'] = pht('Open Tasks');
$names['all'] = pht('All Tasks');
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
$viewer_phid = $this->requireViewer()->getPHID();
switch ($query_key) {
case 'all':
return $query;
case 'assigned':
return $query
->setParameter('assignedPHIDs', array($viewer_phid))
->setParameter(
'statuses',
ManiphestTaskStatus::getOpenStatusConstants());
case 'subscribed':
return $query
->setParameter('subscriberPHIDs', array($viewer_phid))
->setParameter(
'statuses',
ManiphestTaskStatus::getOpenStatusConstants());
case 'open':
return $query
->setParameter(
'statuses',
ManiphestTaskStatus::getOpenStatusConstants());
case 'authored':
return $query
->setParameter('authorPHIDs', array($viewer_phid))
->setParameter('order', 'created')
->setParameter('group', 'none');
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
private function getGroupOptions() {
return array(
'priority' => pht('Priority'),
'assigned' => pht('Assigned'),
'status' => pht('Status'),
'project' => pht('Project'),
'none' => pht('None'),
);
}
private function getGroupValues() {
return array(
'priority' => ManiphestTaskQuery::GROUP_PRIORITY,
'assigned' => ManiphestTaskQuery::GROUP_OWNER,
'status' => ManiphestTaskQuery::GROUP_STATUS,
'project' => ManiphestTaskQuery::GROUP_PROJECT,
'none' => ManiphestTaskQuery::GROUP_NONE,
);
}
protected function renderResultList(
array $tasks,
PhabricatorSavedQuery $saved,
array $handles) {
$viewer = $this->requireViewer();
if ($this->isPanelContext()) {
$can_edit_priority = false;
$can_bulk_edit = false;
} else {
$can_edit_priority = PhabricatorPolicyFilter::hasCapability(
$viewer,
$this->getApplication(),
ManiphestEditPriorityCapability::CAPABILITY);
$can_bulk_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$this->getApplication(),
ManiphestBulkEditCapability::CAPABILITY);
}
$list = id(new ManiphestTaskResultListView())
->setUser($viewer)
->setTasks($tasks)
->setSavedQuery($saved)
->setCanEditPriority($can_edit_priority)
->setCanBatchEdit($can_bulk_edit)
->setShowBatchControls($this->showBatchControls);
$result = new PhabricatorApplicationSearchResultView();
$result->setContent($list);
return $result;
}
protected function willUseSavedQuery(PhabricatorSavedQuery $saved) {
// The 'withUnassigned' parameter may be present in old saved queries from
// before parameterized typeaheads, and is retained for compatibility. We
// could remove it by migrating old saved queries.
$assigned_phids = $saved->getParameter('assignedPHIDs', array());
if ($saved->getParameter('withUnassigned')) {
$assigned_phids[] = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN;
}
$saved->setParameter('assignedPHIDs', $assigned_phids);
// The 'projects' and other parameters may be present in old saved queries
// from before parameterized typeaheads.
$project_phids = $saved->getParameter('projectPHIDs', array());
$old = $saved->getParameter('projects', array());
foreach ($old as $phid) {
$project_phids[] = $phid;
}
$all = $saved->getParameter('allProjectPHIDs', array());
foreach ($all as $phid) {
$project_phids[] = $phid;
}
$any = $saved->getParameter('anyProjectPHIDs', array());
foreach ($any as $phid) {
$project_phids[] = 'any('.$phid.')';
}
$not = $saved->getParameter('excludeProjectPHIDs', array());
foreach ($not as $phid) {
$project_phids[] = 'not('.$phid.')';
}
$users = $saved->getParameter('userProjectPHIDs', array());
foreach ($users as $phid) {
$project_phids[] = 'projects('.$phid.')';
}
$no = $saved->getParameter('withNoProject');
if ($no) {
$project_phids[] = 'null()';
}
$saved->setParameter('projectPHIDs', $project_phids);
}
protected function getNewUserBody() {
$create_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Create a Task'))
->setHref('/maniphest/task/edit/')
->setColor(PHUIButtonView::GREEN);
$icon = $this->getApplication()->getIcon();
$app_name = $this->getApplication()->getName();
$view = id(new PHUIBigInfoView())
->setIcon($icon)
->setTitle(pht('Welcome to %s', $app_name))
->setDescription(
pht('Use Maniphest to track bugs, features, todos, or anything else '.
'you need to get done. Tasks assigned to you will appear here.'))
->addAction($create_button);
return $view;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 14:01 (3 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125325
Default Alt Text
(37 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment