diff --git a/src/applications/project/controller/PhabricatorProjectListController.php b/src/applications/project/controller/PhabricatorProjectListController.php index 139f3f7229..83f9cd3fa4 100644 --- a/src/applications/project/controller/PhabricatorProjectListController.php +++ b/src/applications/project/controller/PhabricatorProjectListController.php @@ -1,182 +1,172 @@ filter = idx($data, 'filter'); } public function processRequest() { $request = $this->getRequest(); $nav = new AphrontSideNavFilterView(); $nav ->setBaseURI(new PhutilURI('/project/filter/')) ->addLabel('User') ->addFilter('active', 'Active') ->addSpacer() ->addLabel('All') ->addFilter('all', 'All Projects') ->addFilter('allactive','Active Projects'); $this->filter = $nav->selectFilter($this->filter, 'active'); $pager = new AphrontPagerView(); $pager->setPageSize(250); $pager->setURI($request->getRequestURI(), 'page'); $pager->setOffset($request->getInt('page')); $query = new PhabricatorProjectQuery(); $query->setViewer($request->getUser()); - $query->setOffset($pager->getOffset()); - $query->setLimit($pager->getPageSize() + 1); + $query->needMembers(true); $view_phid = $request->getUser()->getPHID(); $status_filter = PhabricatorProjectQuery::STATUS_ANY; switch ($this->filter) { case 'active': $table_header = 'Your Projects'; $query->withMemberPHIDs(array($view_phid)); $query->withStatus(PhabricatorProjectQuery::STATUS_ACTIVE); break; case 'allactive': $status_filter = PhabricatorProjectQuery::STATUS_ACTIVE; $table_header = 'Active Projects'; // fallthrough case 'all': $table_header = 'All Projects'; $query->withStatus($status_filter); break; } - $projects = $query->execute(); - $projects = $pager->sliceResults($projects); + $projects = $query->executeWithOffsetPager($pager); $project_phids = mpull($projects, 'getPHID'); $profiles = array(); if ($projects) { $profiles = id(new PhabricatorProjectProfile())->loadAllWhere( 'projectPHID in (%Ls)', $project_phids); $profiles = mpull($profiles, null, 'getProjectPHID'); } - $edge_query = new PhabricatorEdgeQuery(); - if ($projects) { - $edge_query - ->withSourcePHIDs($project_phids) - ->withEdgeTypes(array(PhabricatorEdgeConfig::TYPE_PROJ_MEMBER)) - ->execute(); - } - $tasks = array(); $groups = array(); if ($project_phids) { $query = id(new ManiphestTaskQuery()) ->withProjects($project_phids) ->withAnyProject(true) ->withStatus(ManiphestTaskQuery::STATUS_OPEN) ->setLimit(PHP_INT_MAX); $tasks = $query->execute(); foreach ($tasks as $task) { foreach ($task->getProjectPHIDs() as $phid) { $groups[$phid][] = $task; } } } $rows = array(); foreach ($projects as $project) { $phid = $project->getPHID(); $profile = idx($profiles, $phid); - $members = $edge_query->getDestinationPHIDs(array($phid)); + $members = $project->getMemberPHIDs(); $group = idx($groups, $phid, array()); $task_count = count($group); $population = count($members); if ($profile) { $blurb = $profile->getBlurb(); $blurb = phutil_utf8_shorten($blurb, 64); } else { $blurb = null; } $rows[] = array( phutil_render_tag( 'a', array( 'href' => '/project/view/'.$project->getID().'/', ), phutil_escape_html($project->getName())), phutil_escape_html( PhabricatorProjectStatus::getNameForStatus($project->getStatus())), phutil_escape_html($blurb), phutil_escape_html($population), phutil_render_tag( 'a', array( 'href' => '/maniphest/view/all/?projects='.$phid, ), phutil_escape_html($task_count)), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Project', 'Status', 'Description', 'Population', 'Open Tasks', )); $table->setColumnClasses( array( 'pri', '', 'wide', '', '' )); $panel = new AphrontPanelView(); $panel->setHeader($table_header); $panel->setCreateButton('Create New Project', '/project/create/'); $panel->appendChild($table); $panel->appendChild($pager); $nav->appendChild($panel); return $this->buildStandardPageResponse( $nav, array( 'title' => 'Projects', )); } } diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index f0187227b7..74fd7e5adc 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -1,205 +1,209 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function withMemberPHIDs(array $member_phids) { $this->memberPHIDs = $member_phids; return $this; } public function needMembers($need_members) { $this->needMembers = $need_members; return $this; } protected function getPagingColumn() { return 'name'; } protected function getPagingValue($result) { return $result->getName(); } + protected function getReversePaging() { + return true; + } + public function loadPage() { $table = new PhabricatorProject(); $conn_r = $table->establishConnection('r'); // NOTE: Because visibility checks for projects depend on whether or not // the user is a project member, we always load their membership. If we're // loading all members anyway we can piggyback on that; otherwise we // do an explicit join. $select_clause = ''; if (!$this->needMembers) { $select_clause = ', vm.dst viewerIsMember'; } $data = queryfx_all( $conn_r, 'SELECT p.* %Q FROM %T p %Q %Q %Q %Q %Q', $select_clause, $table->getTableName(), $this->buildJoinClause($conn_r), $this->buildWhereClause($conn_r), $this->buildGroupClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $projects = $table->loadAllFromArray($data); if ($projects) { $viewer_phid = $this->getViewer()->getPHID(); if ($this->needMembers) { $etype = PhabricatorEdgeConfig::TYPE_PROJ_MEMBER; $members = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($projects, 'getPHID')) ->withEdgeTypes(array($etype)) ->execute(); foreach ($projects as $project) { $phid = $project->getPHID(); $project->attachMemberPHIDs(array_keys($members[$phid][$etype])); $project->setIsUserMember( $viewer_phid, isset($members[$phid][$etype][$viewer_phid])); } } else { foreach ($data as $row) { $projects[$row['id']]->setIsUserMember( $viewer_phid, ($row['viewerIsMember'] !== null)); } } } return $projects; } private function buildWhereClause($conn_r) { $where = array(); if ($this->status != self::STATUS_ANY) { switch ($this->status) { case self::STATUS_OPEN: case self::STATUS_ACTIVE: $filter = array( PhabricatorProjectStatus::STATUS_ACTIVE, ); break; case self::STATUS_CLOSED: case self::STATUS_ARCHIVED: $filter = array( PhabricatorProjectStatus::STATUS_ARCHIVED, ); break; default: throw new Exception( "Unknown project status '{$this->status}'!"); } $where[] = qsprintf( $conn_r, 'status IN (%Ld)', $filter); } if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->memberPHIDs) { $where[] = qsprintf( $conn_r, 'e.dst IN (%Ls)', $this->memberPHIDs); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } private function buildGroupClause($conn_r) { if ($this->memberPHIDs) { return 'GROUP BY p.id'; } else { return ''; } } private function buildJoinClause($conn_r) { $joins = array(); if (!$this->needMembers) { $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T vm ON vm.src = p.phid AND vm.type = %d AND vm.dst = %s', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_PROJ_MEMBER, $this->getViewer()->getPHID()); } if ($this->memberPHIDs) { $joins[] = qsprintf( $conn_r, 'JOIN %T e ON e.src = p.phid AND e.type = %d', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_PROJ_MEMBER); } return implode(' ', $joins); } } diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyQuery.php index 8dda36f17d..a3059437ae 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyQuery.php @@ -1,130 +1,138 @@ getID(); } + protected function getReversePaging() { + return false; + } + protected function nextPage(array $page) { if ($this->beforeID) { $this->beforeID = $this->getPagingValue(head($page)); } else { $this->afterID = $this->getPagingValue(last($page)); } } final public function setAfterID($object_id) { $this->afterID = $object_id; return $this; } final public function setBeforeID($object_id) { $this->beforeID = $object_id; return $this; } final protected function buildLimitClause(AphrontDatabaseConnection $conn_r) { if ($this->getRawResultLimit()) { return qsprintf($conn_r, 'LIMIT %d', $this->getRawResultLimit()); } else { return ''; } } final protected function buildPagingClause( AphrontDatabaseConnection $conn_r) { if ($this->beforeID) { return qsprintf( $conn_r, - '%Q > %s', + '%Q %Q %s', $this->getPagingColumn(), + $this->getReversePaging() ? '<' : '>', $this->beforeID); } else if ($this->afterID) { return qsprintf( $conn_r, - '%Q < %s', + '%Q %Q %s', $this->getPagingColumn(), + $this->getReversePaging() ? '>' : '<', $this->afterID); } return null; } final protected function buildOrderClause(AphrontDatabaseConnection $conn_r) { if ($this->beforeID) { return qsprintf( $conn_r, - 'ORDER BY %Q ASC', - $this->getPagingColumn()); + 'ORDER BY %Q %Q', + $this->getPagingColumn(), + $this->getReversePaging() ? 'DESC' : 'ASC'); } else { return qsprintf( $conn_r, - 'ORDER BY %Q DESC', - $this->getPagingColumn()); + 'ORDER BY %Q %Q', + $this->getPagingColumn(), + $this->getReversePaging() ? 'ASC' : 'DESC'); } } final protected function processResults(array $results) { if ($this->beforeID) { $results = array_reverse($results, $preserve_keys = true); } return $results; } final public function executeWithCursorPager(AphrontCursorPagerView $pager) { $this->setLimit($pager->getPageSize() + 1); if ($pager->getAfterID()) { $this->setAfterID($pager->getAfterID()); } else if ($pager->getBeforeID()) { $this->setBeforeID($pager->getBeforeID()); } $results = $this->execute(); $sliced_results = $pager->sliceResults($results); if ($this->beforeID || (count($results) > $pager->getPageSize())) { $pager->setNextPageID($this->getPagingValue(last($sliced_results))); } if ($this->afterID || ($this->beforeID && (count($results) > $pager->getPageSize()))) { $pager->setPrevPageID($this->getPagingValue(head($sliced_results))); } return $sliced_results; } }