diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -20,6 +20,8 @@ private $subpriorityMin; private $subpriorityMax; private $bridgedObjectPHIDs; + private $hasOpenParents; + private $hasOpenSubtasks; private $fullTextSearch = ''; @@ -51,8 +53,6 @@ private $needSubscriberPHIDs; private $needProjectPHIDs; - private $blockingTasks; - private $blockedTasks; public function withAuthors(array $authors) { $this->authorPHIDs = $authors; @@ -151,34 +151,16 @@ return $this; } - /** - * True returns tasks that are blocking other tasks only. - * False returns tasks that are not blocking other tasks only. - * Null returns tasks regardless of blocking status. - */ - public function withBlockingTasks($mode) { - $this->blockingTasks = $mode; + public function withOpenSubtasks($value) { + $this->hasOpenSubtasks = $value; return $this; } - public function shouldJoinBlockingTasks() { - return $this->blockingTasks !== null; - } - - /** - * True returns tasks that are blocked by other tasks only. - * False returns tasks that are not blocked by other tasks only. - * Null returns tasks regardless of blocked by status. - */ - public function withBlockedTasks($mode) { - $this->blockedTasks = $mode; + public function withOpenParents($value) { + $this->hasOpenParents = $value; return $this; } - public function shouldJoinBlockedTasks() { - return $this->blockedTasks !== null; - } - public function withDateCreatedBefore($date_created_before) { $this->dateCreatedBefore = $date_created_before; return $this; @@ -335,7 +317,6 @@ $where = parent::buildWhereClauseParts($conn); $where[] = $this->buildStatusWhereClause($conn); - $where[] = $this->buildDependenciesWhereClause($conn); $where[] = $this->buildOwnerWhereClause($conn); $where[] = $this->buildFullTextWhereClause($conn); @@ -526,71 +507,65 @@ $fulltext_results); } - private function buildDependenciesWhereClause( - AphrontDatabaseConnection $conn) { + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $open_statuses = ManiphestTaskStatus::getOpenStatusConstants(); + $edge_table = PhabricatorEdgeConfig::TABLE_NAME_EDGE; + $task_table = $this->newResultObject()->getTableName(); - if (!$this->shouldJoinBlockedTasks() && - !$this->shouldJoinBlockingTasks()) { - return null; - } + $joins = array(); + if ($this->hasOpenParents !== null) { + $parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; - $parts = array(); - if ($this->blockingTasks === true) { - $parts[] = qsprintf( - $conn, - 'blocking.dst IS NOT NULL AND blockingtask.status IN (%Ls)', - ManiphestTaskStatus::getOpenStatusConstants()); - } else if ($this->blockingTasks === false) { - $parts[] = qsprintf( - $conn, - 'blocking.dst IS NULL OR blockingtask.status NOT IN (%Ls)', - ManiphestTaskStatus::getOpenStatusConstants()); - } + if ($this->hasOpenParents) { + $join_type = 'JOIN'; + } else { + $join_type = 'LEFT JOIN'; + } - if ($this->blockedTasks === true) { - $parts[] = qsprintf( - $conn, - 'blocked.dst IS NOT NULL AND blockedtask.status IN (%Ls)', - ManiphestTaskStatus::getOpenStatusConstants()); - } else if ($this->blockedTasks === false) { - $parts[] = qsprintf( + $joins[] = qsprintf( $conn, - 'blocked.dst IS NULL OR blockedtask.status NOT IN (%Ls)', - ManiphestTaskStatus::getOpenStatusConstants()); + '%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); } - return '('.implode(') OR (', $parts).')'; - } - - protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) { - $edge_table = PhabricatorEdgeConfig::TABLE_NAME_EDGE; + if ($this->hasOpenSubtasks !== null) { + $subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; - $joins = array(); + if ($this->hasOpenSubtasks) { + $join_type = 'JOIN'; + } else { + $join_type = 'LEFT JOIN'; + } - if ($this->shouldJoinBlockingTasks()) { - $joins[] = qsprintf( - $conn_r, - 'LEFT JOIN %T blocking ON blocking.src = task.phid '. - 'AND blocking.type = %d '. - 'LEFT JOIN %T blockingtask ON blocking.dst = blockingtask.phid', - $edge_table, - ManiphestTaskDependedOnByTaskEdgeType::EDGECONST, - id(new ManiphestTask())->getTableName()); - } - if ($this->shouldJoinBlockedTasks()) { $joins[] = qsprintf( - $conn_r, - 'LEFT JOIN %T blocked ON blocked.src = task.phid '. - 'AND blocked.type = %d '. - 'LEFT JOIN %T blockedtask ON blocked.dst = blockedtask.phid', + $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, - ManiphestTaskDependsOnTaskEdgeType::EDGECONST, - id(new ManiphestTask())->getTableName()); + $subtask_type, + $join_type, + $task_table, + $open_statuses); } if ($this->subscriberPHIDs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T e_ccs ON e_ccs.src = task.phid '. 'AND e_ccs.type = %s '. 'AND e_ccs.dst in (%Ls)', @@ -604,7 +579,7 @@ $ignore_group_phids = $this->getIgnoreGroupedProjectPHIDs(); if ($ignore_group_phids) { $joins[] = qsprintf( - $conn_r, + $conn, 'LEFT JOIN %T projectGroup ON task.phid = projectGroup.src AND projectGroup.type = %d AND projectGroup.dst NOT IN (%Ls)', @@ -613,29 +588,30 @@ $ignore_group_phids); } else { $joins[] = qsprintf( - $conn_r, + $conn, 'LEFT JOIN %T projectGroup ON task.phid = projectGroup.src AND projectGroup.type = %d', $edge_table, PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); } $joins[] = qsprintf( - $conn_r, + $conn, 'LEFT JOIN %T projectGroupName ON projectGroup.dst = projectGroupName.indexedObjectPHID', id(new ManiphestNameIndex())->getTableName()); break; } - $joins[] = parent::buildJoinClauseParts($conn_r); + $joins[] = parent::buildJoinClauseParts($conn); return $joins; } protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { - $joined_multiple_rows = $this->shouldJoinBlockingTasks() || - $this->shouldJoinBlockedTasks() || - ($this->shouldGroupQueryResultRows()); + $joined_multiple_rows = + ($this->hasOpenParents !== null) || + ($this->hasOpenSubtasks !== null) || + $this->shouldGroupQueryResultRows(); $joined_project_name = ($this->groupBy == self::GROUP_PROJECT); @@ -652,6 +628,30 @@ } } + + 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: diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -77,19 +77,21 @@ ->setLabel(pht('Contains Words')) ->setKey('fulltext'), id(new PhabricatorSearchThreeStateField()) - ->setLabel(pht('Blocking')) - ->setKey('blocking') + ->setLabel(pht('Open Parents')) + ->setKey('hasParents') + ->setAliases(array('blocking')) ->setOptions( pht('(Show All)'), - pht('Show Only Tasks Blocking Other Tasks'), - pht('Hide Tasks Blocking Other Tasks')), + pht('Show Only Tasks With Open Parents'), + pht('Show Only Tasks Without Open Parents')), id(new PhabricatorSearchThreeStateField()) - ->setLabel(pht('Blocked')) - ->setKey('blocked') + ->setLabel(pht('Open Subtasks')) + ->setKey('hasSubtasks') + ->setAliases(array('blocked')) ->setOptions( pht('(Show All)'), - pht('Show Only Task Blocked By Other Tasks'), - pht('Hide Tasks Blocked By Other Tasks')), + pht('Show Only Tasks With Open Subtasks'), + pht('Show Only Tasks Without Open Subtasks')), id(new PhabricatorSearchSelectField()) ->setLabel(pht('Group By')) ->setKey('group') @@ -121,8 +123,8 @@ 'statuses', 'priorities', 'fulltext', - 'blocking', - 'blocked', + 'hasParents', + 'hasSubtasks', 'group', 'order', 'ids', @@ -182,12 +184,12 @@ $query->withDateModifiedBefore($map['modifiedEnd']); } - if ($map['blocking'] !== null) { - $query->withBlockingTasks($map['blocking']); + if ($map['hasParents'] !== null) { + $query->withOpenParents($map['hasParents']); } - if ($map['blocked'] !== null) { - $query->withBlockedTasks($map['blocked']); + if ($map['hasSubtasks'] !== null) { + $query->withOpenSubtasks($map['hasSubtasks']); } if (strlen($map['fulltext'])) {