1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-03-04 08:29:18 +01:00
phorge-phorge/src/applications/project/controller/PhabricatorProjectProfileController.php

269 lines
7.2 KiB
PHP
Raw Normal View History

<?php
final class PhabricatorProjectProfileController
extends PhabricatorProjectController {
private $id;
private $page;
private $project;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
$this->page = idx($data, 'page');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$query = id(new PhabricatorProjectQuery())
->setViewer($user)
->withIDs(array($this->id))
->needMembers(true);
$project = $query->executeOne();
$this->project = $project;
if (!$project) {
return new Aphront404Response();
}
Improve performance of project list view Summary: D477 added functionality to the project list view but had a couple of performance issues that I missed in review, because it took the query count for the page from around 3 to as many as 300, including up to 100 heavyweight search index queries. This fixes the two simple N+1 query problems. This general pattern of data access often occurs: COUNTEREXAMPLE $cats = load_cats(); foreach ($cats as $cat) { $cats_hats = load_hats_for_cat($cat); // ... } But this issues "N+1" queries, i.e. if you load 100 cats you issue 101 queries. It is faster to group the queries instead: $cats = load_cats(); $hats = load_all_hats_for_these_cats($cats); foreach ($cats as $cat) { $cats_hats = $hats[$cat->getID()]; } MySQL can execute one query which returns all the results much faster than 100 queries which return one result, especially if the database is not local (i.e., over the network). However, this doesn't save a ton of time. The bigger issue is that I didn't have the right keys on the relationship tables in the search engine. This adds them, and reduces the search engine lookup cost from 25-80ms (for secure.phabricator.com) down to 1-3ms. I still probably want to get this out of the loop at some point but it's okay for now and the page loads in a few ms rather than taking more than a second. Test Plan: Used "services" tab, "xhprof" and "EXPLAIN" to analyze page performance. I measured these changes: - Query count: 1 + (3 * N projects) -> 3 + (N projects) (e.g., 301 -> 103) - Total time spent querying, ignoring search indexes: 40ms (local.aprhont.com) -> 20ms (local.aphront.com) - Cost for search index query: 25-80ms (secure.phabricator.com) -> 1-3ms Reviewed By: cadamo Reviewers: cadamo, aran, jungejason, tuomaspelkonen CC: aran, cadamo, epriestley Differential Revision: 485
2011-06-20 16:32:44 -07:00
$profile = $project->loadProfile();
if (!$profile) {
$profile = new PhabricatorProjectProfile();
}
$picture = $profile->loadProfileImageURI();
require_celerity_resource('phabricator-profile-css');
$tasks = $this->renderTasksPage($project, $profile);
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(
array(
$project->getPHID(),
));
$query->setLimit(50);
$query->setViewer($this->getRequest()->getUser());
$stories = $query->execute();
$feed = $this->renderStories($stories);
$people = $this->renderPeoplePage($project, $profile);
$content = id(new AphrontMultiColumnView())
->addColumn($people)
->addColumn($feed)
->setFluidLayout(true);
$content = hsprintf(
'<div class="phabricator-project-layout">%s%s</div>',
$tasks,
$content);
$header = id(new PHUIHeaderView())
->setHeader($project->getName())
->setSubheader(phutil_utf8_shorten($profile->getBlurb(), 1024))
->setImage($picture);
$actions = $this->buildActionListView($project);
$properties = $this->buildPropertyListView($project);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($project->getName()));
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$content,
),
array(
'title' => $project->getName(),
'device' => true,
));
}
private function renderPeoplePage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$member_phids = $project->getMemberPHIDs();
$handles = $this->loadViewerHandles($member_phids);
$affiliated = array();
foreach ($handles as $phids => $handle) {
2013-02-13 14:50:15 -08:00
$affiliated[] = phutil_tag('li', array(), $handle->renderLink());
}
if ($affiliated) {
2013-02-13 14:50:15 -08:00
$affiliated = phutil_tag('ul', array(), $affiliated);
} else {
2013-02-13 14:50:15 -08:00
$affiliated = hsprintf('<p><em>%s</em></p>', pht(
'No one is affiliated with this project.'));
}
2013-02-13 14:50:15 -08:00
return hsprintf(
'<div class="phabricator-profile-info-group profile-wrap-responsive">'.
2013-02-13 14:50:15 -08:00
'<h1 class="phabricator-profile-info-header">%s</h1>'.
'<div class="phabricator-profile-info-pane">%s</div>'.
'</div>',
pht('People'),
$affiliated);
}
private function renderFeedPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(array($project->getPHID()));
$query->setViewer($this->getRequest()->getUser());
$query->setLimit(100);
$stories = $query->execute();
if (!$stories) {
return pht('There are no stories about this project.');
}
return $this->renderStories($stories);
}
private function renderStories(array $stories) {
assert_instances_of($stories, 'PhabricatorFeedStory');
$builder = new PhabricatorFeedBuilder($stories);
$builder->setUser($this->getRequest()->getUser());
$builder->setShowHovercards(true);
$view = $builder->buildView();
2013-02-13 14:50:15 -08:00
return hsprintf(
'<div class="profile-feed profile-wrap-responsive">'.
'%s'.
2013-02-13 14:50:15 -08:00
'</div>',
$view->render());
}
private function renderTasksPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$user = $this->getRequest()->getUser();
$query = id(new ManiphestTaskQuery())
->setViewer($user)
->withAnyProjects(array($project->getPHID()))
->withStatus(ManiphestTaskQuery::STATUS_OPEN)
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
->setLimit(10)
->setCalculateRows(true);
$tasks = $query->execute();
$count = $query->getRowCount();
2011-07-07 16:06:27 -07:00
$phids = mpull($tasks, 'getOwnerPHID');
$phids = array_merge(
$phids,
array_mergev(mpull($tasks, 'getProjectPHIDs')));
2011-07-07 16:06:27 -07:00
$phids = array_filter($phids);
$handles = $this->loadViewerHandles($phids);
2011-07-07 16:06:27 -07:00
$task_list = new ManiphestTaskListView();
$task_list->setUser($user);
$task_list->setTasks($tasks);
$task_list->setHandles($handles);
$open = number_format($count);
2013-02-13 14:50:15 -08:00
$content = hsprintf(
'<div class="phabricator-profile-info-group profile-wrap-responsive">
2013-02-13 14:50:15 -08:00
<h1 class="phabricator-profile-info-header">%s</h1>'.
'<div class="phabricator-profile-info-pane">'.
2013-02-13 14:50:15 -08:00
'%s'.
'</div>
2013-02-13 14:50:15 -08:00
</div>',
pht('Open Tasks (%s)', $open),
$task_list);
return $content;
}
private function buildActionListView(PhabricatorProject $project) {
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $project->getID();
$view = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($project)
->setObjectURI($request->getRequestURI());
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Project'))
->setIcon('edit')
->setHref($this->getApplicationURI("edit/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Members'))
->setIcon('edit')
->setHref($this->getApplicationURI("members/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$action = null;
if (!$project->isUserMember($viewer->getPHID())) {
$can_join = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_JOIN);
$action = id(new PhabricatorActionView())
->setUser($viewer)
->setRenderAsForm(true)
->setHref('/project/update/'.$project->getID().'/join/')
->setIcon('new')
->setDisabled(!$can_join)
->setName(pht('Join Project'));
} else {
$action = id(new PhabricatorActionView())
->setWorkflow(true)
->setHref('/project/update/'.$project->getID().'/leave/')
->setIcon('delete')
->setName(pht('Leave Project...'));
}
$view->addAction($action);
return $view;
}
private function buildPropertyListView(PhabricatorProject $project) {
$request = $this->getRequest();
$viewer = $request->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($project);
$view->addProperty(
pht('Created'),
phabricator_datetime($project->getDateCreated(), $viewer));
return $view;
}
}