2011-02-21 03:41:23 +01:00
|
|
|
<?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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
class PhabricatorProjectProfileController
|
|
|
|
extends PhabricatorProjectController {
|
|
|
|
|
|
|
|
private $id;
|
2011-06-18 10:13:56 +02:00
|
|
|
private $page;
|
2011-02-21 03:41:23 +01:00
|
|
|
|
|
|
|
public function willProcessRequest(array $data) {
|
2011-06-18 10:13:56 +02:00
|
|
|
$this->id = idx($data, 'id');
|
|
|
|
$this->page = idx($data, 'page');
|
2011-02-21 03:41:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function processRequest() {
|
2011-06-18 10:13:56 +02:00
|
|
|
$request = $this->getRequest();
|
|
|
|
$user = $request->getUser();
|
|
|
|
$uri = $request->getRequestURI();
|
2011-02-21 03:41:23 +01:00
|
|
|
|
|
|
|
$project = id(new PhabricatorProject())->load($this->id);
|
|
|
|
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-21 01:32:44 +02:00
|
|
|
$profile = $project->loadProfile();
|
2011-02-21 03:41:23 +01:00
|
|
|
if (!$profile) {
|
|
|
|
$profile = new PhabricatorProjectProfile();
|
|
|
|
}
|
|
|
|
|
|
|
|
$src_phid = $profile->getProfileImagePHID();
|
2011-06-18 10:13:56 +02:00
|
|
|
if (!$src_phid) {
|
|
|
|
$src_phid = $user->getProfileImagePHID();
|
|
|
|
}
|
|
|
|
$picture = PhabricatorFileURI::getViewURIForPHID($src_phid);
|
|
|
|
|
|
|
|
$pages = array(
|
|
|
|
/*
|
|
|
|
'<h2>Active Documents</h2>',
|
|
|
|
'tasks' => 'Maniphest Tasks',
|
|
|
|
'revisions' => 'Differential Revisions',
|
|
|
|
'<hr />',
|
|
|
|
'<h2>Workflow</h2>',
|
|
|
|
'goals' => 'Goals',
|
|
|
|
'statistics' => 'Statistics',
|
|
|
|
'<hr />', */
|
|
|
|
'<h2>Information</h2>',
|
2011-07-19 20:50:15 +02:00
|
|
|
'edit' => 'Edit Project',
|
2011-06-18 10:13:56 +02:00
|
|
|
'affiliation' => 'Edit Affiliation',
|
|
|
|
);
|
|
|
|
|
|
|
|
if (empty($pages[$this->page])) {
|
2011-07-19 20:50:15 +02:00
|
|
|
$this->page = 'action';
|
2011-06-18 10:13:56 +02:00
|
|
|
}
|
2011-02-21 03:41:23 +01:00
|
|
|
|
2011-06-18 10:13:56 +02:00
|
|
|
switch ($this->page) {
|
|
|
|
default:
|
|
|
|
$content = $this->renderBasicInformation($project, $profile);
|
|
|
|
break;
|
|
|
|
}
|
2011-02-21 03:41:23 +01:00
|
|
|
|
2011-06-18 10:13:56 +02:00
|
|
|
$profile = new PhabricatorProfileView();
|
|
|
|
$profile->setProfilePicture($picture);
|
|
|
|
$profile->setProfileNames($project->getName());
|
|
|
|
foreach ($pages as $page => $name) {
|
|
|
|
if (is_integer($page)) {
|
|
|
|
$profile->addProfileItem(
|
|
|
|
phutil_render_tag(
|
|
|
|
'span',
|
|
|
|
array(),
|
|
|
|
$name));
|
|
|
|
} else {
|
|
|
|
$uri->setPath('/project/'.$page.'/'.$project->getID().'/');
|
|
|
|
$profile->addProfileItem(
|
|
|
|
phutil_render_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => $uri,
|
|
|
|
'class' => ($this->page == $page)
|
|
|
|
? 'phabricator-profile-item-selected'
|
|
|
|
: null,
|
|
|
|
),
|
|
|
|
phutil_escape_html($name)));
|
2011-02-21 03:41:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-18 10:13:56 +02:00
|
|
|
$profile->appendChild($content);
|
2011-02-21 03:41:23 +01:00
|
|
|
|
|
|
|
return $this->buildStandardPageResponse(
|
2011-06-18 10:13:56 +02:00
|
|
|
$profile,
|
2011-02-21 03:41:23 +01:00
|
|
|
array(
|
|
|
|
'title' => $project->getName(),
|
2011-06-18 10:13:56 +02:00
|
|
|
));
|
2011-02-21 03:41:23 +01:00
|
|
|
}
|
|
|
|
|
2011-07-19 20:50:15 +02:00
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// Helper functions
|
|
|
|
|
2011-06-18 10:13:56 +02:00
|
|
|
private function renderBasicInformation($project, $profile) {
|
|
|
|
$blurb = nonempty(
|
|
|
|
$profile->getBlurb(),
|
|
|
|
'//Nothing is known about this elusive project.//');
|
|
|
|
|
Generalize the markup engine factory
Summary:
This thing services every app but it lives inside Differential right now. Pull
it out, and separate the factory interfaces per-application.
This will let us accommodate changes we need to make for Phriction to support
wiki linking.
Test Plan: Tested remarkup in differential, diffusion, maniphest, people,
slowvote.
Reviewed By: hsb
Reviewers: hsb, codeblock, jungejason, tuomaspelkonen, aran
CC: aran, hsb
Differential Revision: 646
2011-07-12 00:58:32 +02:00
|
|
|
$engine = PhabricatorMarkupEngine::newProfileMarkupEngine();
|
2011-07-07 23:01:55 +02:00
|
|
|
$blurb = $engine->markupText($blurb);
|
|
|
|
|
|
|
|
$affiliations = $project->loadAffiliations();
|
|
|
|
|
|
|
|
$phids = array_merge(
|
|
|
|
array($project->getAuthorPHID()),
|
2011-07-19 20:50:15 +02:00
|
|
|
$project->getSubprojectPHIDs(),
|
|
|
|
mpull($affiliations, 'getUserPHID')
|
|
|
|
);
|
|
|
|
$phids = array_unique($phids);
|
2011-07-07 23:01:55 +02:00
|
|
|
$handles = id(new PhabricatorObjectHandleData($phids))
|
|
|
|
->loadHandles();
|
|
|
|
|
|
|
|
$affiliated = array();
|
|
|
|
foreach ($affiliations as $affiliation) {
|
|
|
|
$user = $handles[$affiliation->getUserPHID()]->renderLink();
|
|
|
|
$role = phutil_escape_html($affiliation->getRole());
|
|
|
|
|
|
|
|
$status = null;
|
|
|
|
if ($affiliation->getStatus() == 'former') {
|
|
|
|
$role = '<em>Former '.$role.'</em>';
|
|
|
|
}
|
|
|
|
|
|
|
|
$affiliated[] = '<li>'.$user.' — '.$role.$status.'</li>';
|
|
|
|
}
|
2011-07-19 20:50:15 +02:00
|
|
|
|
2011-07-07 23:01:55 +02:00
|
|
|
if ($affiliated) {
|
|
|
|
$affiliated = '<ul>'.implode("\n", $affiliated).'</ul>';
|
|
|
|
} else {
|
|
|
|
$affiliated = '<p><em>No one is affiliated with this project.</em></p>';
|
|
|
|
}
|
|
|
|
|
2011-07-19 20:50:15 +02:00
|
|
|
if ($project->getSubprojectPHIDs()) {
|
|
|
|
$table = $this->renderSubprojectTable(
|
|
|
|
$handles,
|
|
|
|
$project->getSubprojectPHIDs());
|
|
|
|
$subproject_list = $table->render();
|
|
|
|
} else {
|
|
|
|
$subproject_list =
|
|
|
|
'<p><em>There are no projects attached for such specie.</em></p>';
|
|
|
|
}
|
|
|
|
|
2011-12-04 01:36:19 +01:00
|
|
|
$viewer = $this->getRequest()->getUser();
|
|
|
|
$timestamp = phabricator_datetime($project->getDateCreated(), $viewer);
|
2011-07-07 23:01:55 +02:00
|
|
|
$status = PhabricatorProjectStatus::getNameForStatus(
|
|
|
|
$project->getStatus());
|
|
|
|
|
|
|
|
$content =
|
|
|
|
'<div class="phabricator-profile-info-group">
|
|
|
|
<h1 class="phabricator-profile-info-header">Basic Information</h1>
|
|
|
|
<div class="phabricator-profile-info-pane">
|
|
|
|
<table class="phabricator-profile-info-table">
|
|
|
|
<tr>
|
|
|
|
<th>Creator</th>
|
|
|
|
<td>'.$handles[$project->getAuthorPHID()]->renderLink().'</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<th>Status</th>
|
|
|
|
<td><strong>'.phutil_escape_html($status).'</strong></td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<th>Created</th>
|
|
|
|
<td>'.$timestamp.'</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<th>PHID</th>
|
|
|
|
<td>'.phutil_escape_html($project->getPHID()).'</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<th>Blurb</th>
|
|
|
|
<td>'.$blurb.'</td>
|
|
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
</div>';
|
|
|
|
|
|
|
|
$content .=
|
2011-07-19 20:50:15 +02:00
|
|
|
'<div class="phabricator-profile-info-group">'.
|
|
|
|
'<h1 class="phabricator-profile-info-header">Resources</h1>'.
|
|
|
|
'<div class="phabricator-profile-info-pane">'.
|
2011-06-18 10:13:56 +02:00
|
|
|
$affiliated.
|
2011-07-19 20:50:15 +02:00
|
|
|
'</div>'.
|
|
|
|
'</div>';
|
|
|
|
|
|
|
|
$content .= '<div class="phabricator-profile-info-group">'.
|
|
|
|
'<h1 class="phabricator-profile-info-header">Subprojects</h1>'.
|
|
|
|
'<div class="phabricator-profile-info-pane">'.
|
|
|
|
$subproject_list.
|
|
|
|
'</div>'.
|
|
|
|
'</div>';
|
2011-07-07 23:01:55 +02:00
|
|
|
|
|
|
|
$query = id(new ManiphestTaskQuery())
|
|
|
|
->withProjects(array($project->getPHID()))
|
|
|
|
->withStatus(ManiphestTaskQuery::STATUS_OPEN)
|
|
|
|
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
|
|
|
|
->setLimit(10)
|
|
|
|
->setCalculateRows(true);
|
|
|
|
$tasks = $query->execute();
|
|
|
|
$count = $query->getRowCount();
|
|
|
|
|
2011-07-08 01:06:27 +02:00
|
|
|
$phids = mpull($tasks, 'getOwnerPHID');
|
|
|
|
$phids = array_filter($phids);
|
|
|
|
$handles = id(new PhabricatorObjectHandleData($phids))
|
|
|
|
->loadHandles();
|
|
|
|
|
2011-07-07 23:01:55 +02:00
|
|
|
$task_views = array();
|
|
|
|
foreach ($tasks as $task) {
|
|
|
|
$view = id(new ManiphestTaskSummaryView())
|
|
|
|
->setTask($task)
|
|
|
|
->setHandles($handles)
|
|
|
|
->setUser($this->getRequest()->getUser());
|
|
|
|
$task_views[] = $view->render();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (empty($tasks)) {
|
|
|
|
$task_views = '<em>No open tasks.</em>';
|
|
|
|
} else {
|
|
|
|
$task_views = implode('', $task_views);
|
|
|
|
}
|
|
|
|
|
|
|
|
$open = number_format($count);
|
|
|
|
|
|
|
|
$more_link = phutil_render_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => '/maniphest/view/all/?projects='.$project->getPHID(),
|
|
|
|
),
|
|
|
|
"View All Open Tasks \xC2\xBB");
|
|
|
|
|
|
|
|
$content .=
|
|
|
|
'<div class="phabricator-profile-info-group">
|
|
|
|
<h1 class="phabricator-profile-info-header">'.
|
|
|
|
"Open Tasks ({$open})".
|
|
|
|
'</h1>'.
|
|
|
|
'<div class="phabricator-profile-info-pane">'.
|
|
|
|
$task_views.
|
|
|
|
'<div class="phabricator-profile-info-pane-more-link">'.
|
|
|
|
$more_link.
|
|
|
|
'</div>'.
|
|
|
|
'</div>
|
|
|
|
</div>';
|
2011-06-18 10:13:56 +02:00
|
|
|
|
|
|
|
return $content;
|
|
|
|
}
|
2011-07-19 20:50:15 +02:00
|
|
|
|
|
|
|
private function renderSubprojectTable(
|
|
|
|
PhabricatorObjectHandleData $handles,
|
|
|
|
$subprojects_phids) {
|
|
|
|
|
|
|
|
$rows = array();
|
|
|
|
foreach ($subprojects_phids as $subproject_phid) {
|
|
|
|
$phid = $handles[$subproject_phid]->getPHID();
|
|
|
|
|
|
|
|
$rows[] = array(
|
|
|
|
phutil_escape_html($handles[$phid]->getFullName()),
|
|
|
|
phutil_render_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'class' => 'small grey button',
|
|
|
|
'href' => $handles[$phid]->getURI(),
|
|
|
|
),
|
|
|
|
'View Project Profile'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$table = new AphrontTableView($rows);
|
|
|
|
$table->setHeaders(
|
|
|
|
array(
|
|
|
|
'Name',
|
|
|
|
'',
|
|
|
|
));
|
|
|
|
$table->setColumnClasses(
|
|
|
|
array(
|
|
|
|
'pri',
|
|
|
|
'action right',
|
|
|
|
));
|
|
|
|
|
|
|
|
return $table;
|
|
|
|
}
|
2011-02-21 03:41:23 +01:00
|
|
|
}
|