1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-20 20:40:56 +01:00

Take the first step on the long journey of fixing "Projects"

Summary:
  - Allow more than the 100 most recent projects to be viewed.
  - Provide some useful filters.
  - Default the view to your projects, not all projects.
  - Put query logic in a query object.
  - Put filter view logic in a view object. We can port more stuff to it later.

Test Plan: Looked at active/owned/all projects. Set page size to 5 and paged
through projects.

Reviewers: btrahan, jungejason, zeeg

Reviewed By: btrahan

CC: aran, btrahan

Differential Revision: 1227
This commit is contained in:
epriestley 2011-12-16 17:08:18 -08:00
parent 770beb5cd5
commit 81acf588e2
8 changed files with 322 additions and 7 deletions

View file

@ -73,6 +73,7 @@ phutil_register_library_map(array(
'AphrontRequestFailureView' => 'view/page/failure', 'AphrontRequestFailureView' => 'view/page/failure',
'AphrontResponse' => 'aphront/response/base', 'AphrontResponse' => 'aphront/response/base',
'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/scopeguard', 'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/scopeguard',
'AphrontSideNavFilterView' => 'view/layout/sidenavfilter',
'AphrontSideNavView' => 'view/layout/sidenav', 'AphrontSideNavView' => 'view/layout/sidenav',
'AphrontTableView' => 'view/control/table', 'AphrontTableView' => 'view/control/table',
'AphrontTokenizerTemplateView' => 'view/control/tokenizer', 'AphrontTokenizerTemplateView' => 'view/control/tokenizer',
@ -568,6 +569,7 @@ phutil_register_library_map(array(
'PhabricatorProjectProfile' => 'applications/project/storage/profile', 'PhabricatorProjectProfile' => 'applications/project/storage/profile',
'PhabricatorProjectProfileController' => 'applications/project/controller/profile', 'PhabricatorProjectProfileController' => 'applications/project/controller/profile',
'PhabricatorProjectProfileEditController' => 'applications/project/controller/profileedit', 'PhabricatorProjectProfileEditController' => 'applications/project/controller/profileedit',
'PhabricatorProjectQuery' => 'applications/project/query/project',
'PhabricatorProjectStatus' => 'applications/project/constants/status', 'PhabricatorProjectStatus' => 'applications/project/constants/status',
'PhabricatorProjectSubproject' => 'applications/project/storage/subproject', 'PhabricatorProjectSubproject' => 'applications/project/storage/subproject',
'PhabricatorRedirectController' => 'applications/base/controller/redirect', 'PhabricatorRedirectController' => 'applications/base/controller/redirect',
@ -815,6 +817,7 @@ phutil_register_library_map(array(
'AphrontRedirectResponse' => 'AphrontResponse', 'AphrontRedirectResponse' => 'AphrontResponse',
'AphrontReloadResponse' => 'AphrontRedirectResponse', 'AphrontReloadResponse' => 'AphrontRedirectResponse',
'AphrontRequestFailureView' => 'AphrontView', 'AphrontRequestFailureView' => 'AphrontView',
'AphrontSideNavFilterView' => 'AphrontView',
'AphrontSideNavView' => 'AphrontView', 'AphrontSideNavView' => 'AphrontView',
'AphrontTableView' => 'AphrontView', 'AphrontTableView' => 'AphrontView',
'AphrontTokenizerTemplateView' => 'AphrontView', 'AphrontTokenizerTemplateView' => 'AphrontView',

View file

@ -199,6 +199,7 @@ class AphrontDefaultApplicationConfiguration
'/project/' => array( '/project/' => array(
'$' => 'PhabricatorProjectListController', '$' => 'PhabricatorProjectListController',
'filter/(?P<filter>[^/]+)/$' => 'PhabricatorProjectListController',
'edit/(?P<id>\d+)/$' => 'PhabricatorProjectProfileEditController', 'edit/(?P<id>\d+)/$' => 'PhabricatorProjectProfileEditController',
'view/(?P<id>\d+)/(?:(?P<page>\w+)/)?$' 'view/(?P<id>\d+)/(?:(?P<page>\w+)/)?$'
=> 'PhabricatorProjectProfileController', => 'PhabricatorProjectProfileController',

View file

@ -19,10 +19,54 @@
class PhabricatorProjectListController class PhabricatorProjectListController
extends PhabricatorProjectController { extends PhabricatorProjectController {
public function processRequest() { private $filter;
public function willProcessRequest(array $data) {
$this->filter = idx($data, 'filter');
}
public function processRequest() {
$request = $this->getRequest();
$nav = new AphrontSideNavFilterView();
$nav
->setBaseURI(new PhutilURI('/project/filter/'))
->addLabel('User')
->addFilter('active', 'Active')
->addFilter('owned', 'Owned')
->addSpacer()
->addLabel('All')
->addFilter('all', 'All 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->setOffset($pager->getOffset());
$query->setLimit($pager->getPageSize() + 1);
$view_phid = $request->getUser()->getPHID();
switch ($this->filter) {
case 'active':
$table_header = 'Active Projects';
$query->setMembers(array($view_phid));
break;
case 'owned':
$table_header = 'Owned Projects';
$query->setOwners(array($view_phid));
break;
case 'all':
$table_header = 'All Projects';
break;
}
$projects = $query->execute();
$projects = $pager->sliceResults($projects);
$projects = id(new PhabricatorProject())->loadAllWhere(
'1 = 1 ORDER BY id DESC limit 100');
$project_phids = mpull($projects, 'getPHID'); $project_phids = mpull($projects, 'getPHID');
$profiles = array(); $profiles = array();
@ -121,12 +165,15 @@ class PhabricatorProjectListController
)); ));
$panel = new AphrontPanelView(); $panel = new AphrontPanelView();
$panel->appendChild($table); $panel->setHeader($table_header);
$panel->setHeader('Project');
$panel->setCreateButton('Create New Project', '/project/create/'); $panel->setCreateButton('Create New Project', '/project/create/');
$panel->appendChild($table);
$panel->appendChild($pager);
$nav->appendChild($panel);
return $this->buildStandardPageResponse( return $this->buildStandardPageResponse(
$panel, $nav,
array( array(
'title' => 'Projects', 'title' => 'Projects',
)); ));

View file

@ -10,13 +10,16 @@ phutil_require_module('phabricator', 'applications/maniphest/query');
phutil_require_module('phabricator', 'applications/phid/handle/data'); phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'applications/project/constants/status'); phutil_require_module('phabricator', 'applications/project/constants/status');
phutil_require_module('phabricator', 'applications/project/controller/base'); phutil_require_module('phabricator', 'applications/project/controller/base');
phutil_require_module('phabricator', 'applications/project/query/project');
phutil_require_module('phabricator', 'applications/project/storage/affiliation'); phutil_require_module('phabricator', 'applications/project/storage/affiliation');
phutil_require_module('phabricator', 'applications/project/storage/profile'); phutil_require_module('phabricator', 'applications/project/storage/profile');
phutil_require_module('phabricator', 'applications/project/storage/project'); phutil_require_module('phabricator', 'view/control/pager');
phutil_require_module('phabricator', 'view/control/table'); phutil_require_module('phabricator', 'view/control/table');
phutil_require_module('phabricator', 'view/layout/panel'); phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'view/layout/sidenavfilter');
phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'parser/uri');
phutil_require_module('phutil', 'utils'); phutil_require_module('phutil', 'utils');

View file

@ -0,0 +1,103 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorProjectQuery {
private $owners;
private $members;
private $limit;
private $offset;
public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
public function setOffset($offset) {
$this->offset = $offset;
return $this;
}
public function setOwners(array $owners) {
$this->owners = $owners;
return $this;
}
public function setMembers(array $members) {
$this->members = $members;
return $this;
}
public function execute() {
$table = id(new PhabricatorProject());
$conn_r = $table->establishConnection('r');
$joins = $this->buildJoinsClause($conn_r);
$limit = null;
if ($this->limit) {
$limit = qsprintf(
$conn_r,
'LIMIT %d, %d',
$this->offset,
$this->limit);
} else if ($this->offset) {
$limit = qsprintf(
$conn_r,
'LIMIT %d, %d',
$this->offset,
PHP_INT_MAX);
}
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T p %Q %Q',
$table->getTableName(),
$joins,
$limit);
return $table->loadAllFromArray($data);
}
private function buildJoinsClause($conn_r) {
$affil_table = new PhabricatorProjectAffiliation();
$joins = array();
if ($this->owners) {
$joins[] = qsprintf(
$conn_r,
'JOIN %T owner ON owner.projectPHID = p.phid AND owner.isOwner = 1
AND owner.userPHID in (%Ls)',
$affil_table->getTableName(),
$this->owners);
}
if ($this->members) {
$joins[] = qsprintf(
$conn_r,
'JOIN %T member ON member.projectPHID = p.phid
AND member.userPHID in (%Ls)',
$affil_table->getTableName(),
$this->members);
}
return implode(' ', $joins);
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/project/storage/affiliation');
phutil_require_module('phabricator', 'applications/project/storage/project');
phutil_require_module('phabricator', 'storage/qsprintf');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorProjectQuery.php');

View file

@ -0,0 +1,126 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Like an @{class:AphrontSideNavView}, but with a little bit of logic for the
* common case where you're using the side nav to filter some view of objects.
*
* For example:
*
* $nav = new AphrontSideNavFilterView();
* $nav
* ->setBaseURI($some_uri)
* ->addLabel('Cats')
* ->addFilter('meow', 'Meow')
* ->addFilter('purr', 'Purr')
* ->addSpacer()
* ->addLabel('Dogs')
* ->addFilter('woof', 'Woof')
* ->addFilter('bark', 'Bark');
* $valid_filter = $nav->selectFilter($user_selection, $default = 'meow');
*
*/
final class AphrontSideNavFilterView extends AphrontView {
private $items = array();
private $baseURI;
private $selectedFilter = false;
public function addFilter($key, $name) {
$this->items[] = array('filter', $key, $name);
return $this;
}
public function addLabel($name) {
$this->items[] = array('label', null, $name);
return $this;
}
public function addSpacer() {
$this->items[] = array('spacer', null, null);
return $this;
}
public function setBaseURI(PhutilURI $uri) {
$this->baseURI = $uri;
return $this;
}
public function selectFilter($key, $default) {
$this->selectedFilter = $default;
foreach ($this->items as $item) {
if ($item[0] == 'filter') {
if ($item[1] == $key) {
$this->selectedFilter = $key;
break;
}
}
}
return $this->selectedFilter;
}
public function render() {
if (!$this->baseURI) {
throw new Exception("Call setBaseURI() before render()!");
}
if ($this->selectedFilter === false) {
throw new Exception("Call selectFilter() before render()!");
}
$view = new AphrontSideNavView();
foreach ($this->items as $item) {
list($type, $key, $name) = $item;
switch ($type) {
case 'spacer':
$view->addNavItem('<br />');
break;
case 'label':
$view->addNavItem(
phutil_render_tag(
'span',
array(),
phutil_escape_html($name)));
break;
case 'filter':
$class = ($key == $this->selectedFilter)
? 'aphront-side-nav-selected'
: null;
$href = clone $this->baseURI;
$href->setPath($href->getPath().$key.'/');
$href = (string)$href;
$view->addNavItem(
phutil_render_tag(
'a',
array(
'href' => $href,
'class' => $class,
),
phutil_escape_html($name)));
break;
default:
throw new Exception("Unknown item type '{$type}'.");
}
}
$view->appendChild($this->renderChildren());
return $view->render();
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'view/base');
phutil_require_module('phabricator', 'view/layout/sidenav');
phutil_require_module('phutil', 'markup');
phutil_require_source('AphrontSideNavFilterView.php');