diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index facd569fc2..d9e74a5487 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1402,6 +1402,7 @@ phutil_register_library_map(array( 'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php', 'PhabricatorProjectProfileEditController' => 'applications/project/controller/PhabricatorProjectProfileEditController.php', 'PhabricatorProjectQuery' => 'applications/project/query/PhabricatorProjectQuery.php', + 'PhabricatorProjectSearchEngine' => 'applications/project/query/PhabricatorProjectSearchEngine.php', 'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php', 'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php', 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', @@ -3394,13 +3395,18 @@ phutil_register_library_map(array( 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 'PhabricatorProjectEditor' => 'PhabricatorEditor', 'PhabricatorProjectEditorTestCase' => 'PhabricatorTestCase', - 'PhabricatorProjectListController' => 'PhabricatorProjectController', + 'PhabricatorProjectListController' => + array( + 0 => 'PhabricatorProjectController', + 1 => 'PhabricatorApplicationSearchResultsControllerInterface', + ), 'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController', 'PhabricatorProjectNameCollisionException' => 'Exception', 'PhabricatorProjectProfile' => 'PhabricatorProjectDAO', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorProjectProfileEditController' => 'PhabricatorProjectController', 'PhabricatorProjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorProjectSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorProjectTransaction' => 'PhabricatorProjectDAO', 'PhabricatorProjectTransactionType' => 'PhabricatorProjectConstants', diff --git a/src/applications/project/application/PhabricatorApplicationProject.php b/src/applications/project/application/PhabricatorApplicationProject.php index 9e43d1ed87..9ef71d2a6b 100644 --- a/src/applications/project/application/PhabricatorApplicationProject.php +++ b/src/applications/project/application/PhabricatorApplicationProject.php @@ -35,7 +35,7 @@ final class PhabricatorApplicationProject extends PhabricatorApplication { public function getRoutes() { return array( '/project/' => array( - '' => 'PhabricatorProjectListController', + '(?:query/(?P[^/]+)/)?' => 'PhabricatorProjectListController', 'filter/(?P[^/]+)/' => 'PhabricatorProjectListController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorProjectProfileEditController', 'members/(?P[1-9]\d*)/' diff --git a/src/applications/project/constants/PhabricatorProjectStatus.php b/src/applications/project/constants/PhabricatorProjectStatus.php index 20e0eb0757..16f4a98332 100644 --- a/src/applications/project/constants/PhabricatorProjectStatus.php +++ b/src/applications/project/constants/PhabricatorProjectStatus.php @@ -14,15 +14,6 @@ final class PhabricatorProjectStatus { return idx($map, coalesce($status, '?'), pht('Unknown')); } - public static function getIconForStatus($status) { - $map = array( - self::STATUS_ACTIVE => 'check', - self::STATUS_ARCHIVED => 'disable', - ); - - return idx($map, $status); - } - public static function getStatusMap() { return array( self::STATUS_ACTIVE => pht('Active'), diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php index f3f3d133a7..1c6810e4b0 100644 --- a/src/applications/project/controller/PhabricatorProjectController.php +++ b/src/applications/project/controller/PhabricatorProjectController.php @@ -2,23 +2,22 @@ abstract class PhabricatorProjectController extends PhabricatorController { - public function buildSideNavView($filter = null, $for_app = false) { + public function buildSideNavView($for_app = false) { $user = $this->getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); - $nav - ->setBaseURI(new PhutilURI('/project/filter/')) - ->addLabel(pht('User')) - ->addFilter('active', pht('Active')) - ->addLabel(pht('All')) - ->addFilter('all', pht('All Projects')) - ->addFilter('allactive', pht('Active Projects')) - ->selectFilter($filter, 'active'); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); if ($for_app) { - $nav->addFilter('create/', pht('Create Project')); + $nav->addFilter('create', pht('Create Project')); } + id(new PhabricatorProjectSearchEngine()) + ->setViewer($user) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + return $nav; } diff --git a/src/applications/project/controller/PhabricatorProjectListController.php b/src/applications/project/controller/PhabricatorProjectListController.php index fcd79bfeb4..1589fb629b 100644 --- a/src/applications/project/controller/PhabricatorProjectListController.php +++ b/src/applications/project/controller/PhabricatorProjectListController.php @@ -1,151 +1,54 @@ filter = idx($data, 'filter'); + $this->queryKey = idx($data, 'queryKey'); } public function processRequest() { $request = $this->getRequest(); + $controller = id(new PhabricatorApplicationSearchController($request)) + ->setQueryKey($this->queryKey) + ->setSearchEngine(new PhabricatorProjectSearchEngine()) + ->setNavigation($this->buildSideNavView()); - $nav = $this->buildSideNavView($this->filter); - $this->filter = $nav->selectFilter($this->filter, 'active'); + return $this->delegateToController($controller); + } - $pager = new AphrontPagerView(); - $pager->setPageSize(250); - $pager->setURI($request->getRequestURI(), 'page'); - $pager->setOffset($request->getInt('page')); - - $query = new PhabricatorProjectQuery(); - $query->setViewer($request->getUser()); - $query->needMembers(true); - - $view_phid = $request->getUser()->getPHID(); - - switch ($this->filter) { - case 'active': - $table_header = pht('Your Projects'); - $query->withMemberPHIDs(array($view_phid)); - $query->withStatus(PhabricatorProjectQuery::STATUS_ACTIVE); - break; - case 'allactive': - $table_header = pht('Active Projects'); - $query->withStatus(PhabricatorProjectQuery::STATUS_ACTIVE); - break; - case 'all': - $table_header = pht('All Projects'); - $query->withStatus(PhabricatorProjectQuery::STATUS_ANY); - break; - } - - $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'); - } - - $tasks = array(); - $groups = array(); - if ($project_phids) { - $query = id(new ManiphestTaskQuery()) - ->withAnyProjects($project_phids) - ->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 = $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; - } - - $tasks_href = pht('%d Open Task(s)', $task_count); - - $rows[] = array( - $project->getName(), - '/project/view/'.$project->getID().'/', - PhabricatorProjectStatus::getNameForStatus($project->getStatus()), - PhabricatorProjectStatus::getIconForStatus($project->getStatus()), - $blurb, - pht('%d Member(s)', $population), - phutil_tag( - 'a', - array( - 'href' => '/maniphest/view/all/?projects='.$phid, - ), - $tasks_href), - '/project/edit/'.$project->getID().'/', - ); - } + public function renderResultsList( + array $projects, + PhabricatorSavedQuery $query) { + assert_instances_of($projects, 'PhabricatorProject'); + $viewer = $this->getRequest()->getUser(); $list = new PhabricatorObjectItemListView(); - $list->setStackable(true); - foreach ($rows as $row) { + $list->setUser($viewer); + foreach ($projects as $project) { + $id = $project->getID(); + $item = id(new PhabricatorObjectItemView()) - ->setHeader($row[0]) - ->setHref($row[1]) - ->addIcon($row[3], $row[2]) - ->addIcon('edit', pht('Edit Project'), array('href' => $row[7])); - if ($row[4]) { - $item->addAttribute($row[4]); + ->setHeader($project->getName()) + ->setHref($this->getApplicationURI("view/{$id}/")); + + if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) { + $item->addIcon('delete-grey', pht('Archived')); + $item->setDisabled(true); } - $item->addAttribute($row[5]); - $item->addAttribute($row[6]); + + $list->addItem($item); } - $nav->appendChild( - array( - $list, - $pager, - )); - - $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); - $crumbs->addCrumb( - id(new PhabricatorCrumbView()) - ->setName($table_header) - ->setHref($this->getApplicationURI())); - $nav->setCrumbs($crumbs); - - return $this->buildApplicationPage( - $nav, - array( - 'title' => pht('Projects'), - 'device' => true, - 'dust' => true, - )); + return $list; } - public function buildApplicationMenu() { - return $this->buildSideNavView(null, true)->getMenu(); - } } diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php new file mode 100644 index 0000000000..72bf5990ed --- /dev/null +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -0,0 +1,112 @@ +setParameter('memberPHIDs', $request->getArr('memberPHIDs')); + $saved->setParameter('status', $request->getStr('status')); + + return $saved; + } + + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { + $query = id(new PhabricatorProjectQuery()); + + $member_phids = $saved->getParameter('memberPHIDs', array()); + if ($member_phids && is_array($member_phids)) { + $query->withMemberPHIDs($member_phids); + } + + $status = $saved->getParameter('status'); + $status = idx($this->getStatusValues(), $status); + if ($status) { + $query->withStatus($status); + } + + return $query; + } + + public function buildSearchForm( + AphrontFormView $form, + PhabricatorSavedQuery $saved_query) { + + $phids = $saved_query->getParameter('memberPHIDs', array()); + $handles = id(new PhabricatorObjectHandleData($phids)) + ->setViewer($this->requireViewer()) + ->loadHandles(); + $member_tokens = mpull($handles, 'getFullName', 'getPHID'); + + $status = $saved_query->getParameter('status'); + + $form + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setDatasource('/typeahead/common/users/') + ->setName('memberPHIDs') + ->setLabel(pht('Members')) + ->setValue($member_tokens)) + ->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Status')) + ->setName('status') + ->setOptions($this->getStatusOptions()) + ->setValue($status)); + } + + protected function getURI($path) { + return '/project/'.$path; + } + + public function getBuiltinQueryNames() { + $names = array(); + + if ($this->requireViewer()->isLoggedIn()) { + $names['joined'] = pht('Joined'); + } + + $names['active'] = pht('Active'); + $names['all'] = pht('All'); + + 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 'active': + return $query + ->setParameter('status', 'active'); + case 'joined': + return $query + ->setParameter('memberPHIDs', array($viewer_phid)) + ->setParameter('status', 'active'); + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + private function getStatusOptions() { + return array( + 'active' => pht('Show Only Active Projects'), + 'all' => pht('Show All Projects'), + ); + } + + private function getStatusValues() { + return array( + 'active' => PhabricatorProjectQuery::STATUS_ACTIVE, + 'all' => PhabricatorProjectQuery::STATUS_ANY, + ); + } + +}