diff --git a/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php index 45373d2587..7ddf3b03eb 100644 --- a/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php @@ -1,132 +1,135 @@ formatStringConstants($statuses); $orders = array( ManiphestTaskQuery::ORDER_PRIORITY, ManiphestTaskQuery::ORDER_CREATED, ManiphestTaskQuery::ORDER_MODIFIED, ); $order_const = $this->formatStringConstants($orders); return array( 'ids' => 'optional list', 'phids' => 'optional list', 'ownerPHIDs' => 'optional list', 'authorPHIDs' => 'optional list', 'projectPHIDs' => 'optional list', 'ccPHIDs' => 'optional list', 'fullText' => 'optional string', 'status' => 'optional '.$status_const, 'order' => 'optional '.$order_const, 'limit' => 'optional int', 'offset' => 'optional int', ); } protected function defineReturnType() { return 'list'; } protected function execute(ConduitAPIRequest $request) { $query = id(new ManiphestTaskQuery()) ->setViewer($request->getUser()) ->needProjectPHIDs(true) ->needSubscriberPHIDs(true); $task_ids = $request->getValue('ids'); if ($task_ids) { $query->withIDs($task_ids); } $task_phids = $request->getValue('phids'); if ($task_phids) { $query->withPHIDs($task_phids); } $owners = $request->getValue('ownerPHIDs'); if ($owners) { $query->withOwners($owners); } $authors = $request->getValue('authorPHIDs'); if ($authors) { $query->withAuthors($authors); } $projects = $request->getValue('projectPHIDs'); if ($projects) { $query->withEdgeLogicPHIDs( PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, PhabricatorQueryConstraint::OPERATOR_AND, $projects); } $ccs = $request->getValue('ccPHIDs'); if ($ccs) { $query->withSubscribers($ccs); } $full_text = $request->getValue('fullText'); if ($full_text) { - $query->withFullTextSearch($full_text); + throw new Exception( + pht( + 'Parameter "fullText" is no longer supported. Use method '. + '"maniphest.search" with the "query" constraint instead.')); } $status = $request->getValue('status'); if ($status) { $query->withStatus($status); } $order = $request->getValue('order'); if ($order) { $query->setOrder($order); } $limit = $request->getValue('limit'); if ($limit) { $query->setLimit($limit); } $offset = $request->getValue('offset'); if ($offset) { $query->setOffset($offset); } $results = $query->execute(); return $this->buildTaskInfoDictionaries($results); } } diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index f009e5c2ef..cfc69722d8 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -1,887 +1,849 @@ 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 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 withSubtypes(array $subtypes) { $this->subtypes = $subtypes; 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; } $data = $this->didLoadRawRows($data); $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->bridgedObjectPHIDs !== null) { $where[] = qsprintf( $conn, 'task.bridgedObjectPHID IN (%Ls)', $this->bridgedObjectPHIDs); } if ($this->subtypes !== null) { $where[] = qsprintf( $conn, 'task.subtype IN (%Ls)', $this->subtypes); } 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 10,000 will raise errors in - // Elasticsearch, and billions of results won't fit in memory anyway. - $fulltext_query->setParameter('limit', 10000); - $fulltext_query->setParameter('types', - array(ManiphestTaskPHIDType::TYPECONST)); - - $fulltext_results = PhabricatorSearchService::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) { 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) { 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 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 150ec81def..ec1956bd8e 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -1,443 +1,435 @@ 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('Maniphest Tasks'); } public function getApplicationClassName() { return 'PhabricatorManiphestApplication'; } public function newQuery() { return id(new ManiphestTaskQuery()) ->needProjectPHIDs(true); } protected function buildCustomSearchFields() { // Hide the "Subtypes" constraint from the web UI if the install only // defines one task subtype, since it isn't of any use in this case. $subtype_map = id(new ManiphestTask())->newEditEngineSubtypeMap(); $hide_subtypes = (count($subtype_map) == 1); 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 PhabricatorSearchDatasourceField()) ->setLabel(pht('Subtypes')) ->setKey('subtypes') ->setAliases(array('subtype')) ->setDescription( pht('Search for tasks with given subtypes.')) ->setDatasource(new ManiphestTaskSubtypeDatasource()) ->setIsHidden($hide_subtypes), - 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', 'subtypes', - '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['subtypes']) { $query->withSubtypes($map['subtypes']); } 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); } 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() { $viewer = $this->requireViewer(); $create_button = id(new ManiphestEditEngine()) ->setViewer($viewer) ->newNUXBUtton(pht('Create a Task')); $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; } }