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

Simplify ProjectQuery handling of viewer membership

Summary:
Ref T10010. Currently, we do an unusual JOIN to make testing for viewer membership in projects a little cheaper.

This won't work as-is once we have subprojects, so standardize, simplify, and cover it with more tests for now. (I may be able to get a similar optimization later, but want a correct implementation first.)

Test Plan:
This change should create no behavioral differences.

  - Added tests.
  - Ran tests.
  - Viewed projects.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10010

Differential Revision: https://secure.phabricator.com/D14859
This commit is contained in:
epriestley 2015-12-23 03:42:42 -08:00
parent c4df80b39e
commit 16d8e806a0
3 changed files with 134 additions and 78 deletions

View file

@ -2834,6 +2834,7 @@ phutil_register_library_map(array(
'PhabricatorProjectConfigOptions' => 'applications/project/config/PhabricatorProjectConfigOptions.php', 'PhabricatorProjectConfigOptions' => 'applications/project/config/PhabricatorProjectConfigOptions.php',
'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php', 'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php',
'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php', 'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php',
'PhabricatorProjectCoreTestCase' => 'applications/project/__tests__/PhabricatorProjectCoreTestCase.php',
'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php', 'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php',
'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php', 'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php',
'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php', 'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php',
@ -2843,7 +2844,6 @@ phutil_register_library_map(array(
'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php', 'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php',
'PhabricatorProjectEditDetailsController' => 'applications/project/controller/PhabricatorProjectEditDetailsController.php', 'PhabricatorProjectEditDetailsController' => 'applications/project/controller/PhabricatorProjectEditDetailsController.php',
'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php',
'PhabricatorProjectEditorTestCase' => 'applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php',
'PhabricatorProjectFeedController' => 'applications/project/controller/PhabricatorProjectFeedController.php', 'PhabricatorProjectFeedController' => 'applications/project/controller/PhabricatorProjectFeedController.php',
'PhabricatorProjectFulltextEngine' => 'applications/project/search/PhabricatorProjectFulltextEngine.php', 'PhabricatorProjectFulltextEngine' => 'applications/project/search/PhabricatorProjectFulltextEngine.php',
'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php', 'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php',
@ -7168,6 +7168,7 @@ phutil_register_library_map(array(
'PhabricatorStandardCustomFieldInterface', 'PhabricatorStandardCustomFieldInterface',
), ),
'PhabricatorProjectController' => 'PhabricatorController', 'PhabricatorProjectController' => 'PhabricatorController',
'PhabricatorProjectCoreTestCase' => 'PhabricatorTestCase',
'PhabricatorProjectCustomField' => 'PhabricatorCustomField', 'PhabricatorProjectCustomField' => 'PhabricatorCustomField',
'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage', 'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
@ -7177,7 +7178,6 @@ phutil_register_library_map(array(
'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField', 'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField',
'PhabricatorProjectEditDetailsController' => 'PhabricatorProjectController', 'PhabricatorProjectEditDetailsController' => 'PhabricatorProjectController',
'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController',
'PhabricatorProjectEditorTestCase' => 'PhabricatorTestCase',
'PhabricatorProjectFeedController' => 'PhabricatorProjectController', 'PhabricatorProjectFeedController' => 'PhabricatorProjectController',
'PhabricatorProjectFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorProjectFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorProjectHeraldAction' => 'HeraldAction', 'PhabricatorProjectHeraldAction' => 'HeraldAction',

View file

@ -1,6 +1,6 @@
<?php <?php
final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase { final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() { protected function getPhabricatorTestCaseConfiguration() {
return array( return array(
@ -39,6 +39,49 @@ final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase {
$this->assertFalse((bool)$this->refreshProject($proj, $user2)); $this->assertFalse((bool)$this->refreshProject($proj, $user2));
} }
public function testIsViewerMemberOrWatcher() {
$user1 = $this->createUser()
->save();
$user2 = $this->createUser()
->save();
$user3 = $this->createUser()
->save();
$proj1 = $this->createProject($user1);
$proj1 = $this->refreshProject($proj1, $user1);
$this->joinProject($proj1, $user1);
$this->joinProject($proj1, $user3);
$this->watchProject($proj1, $user3);
$proj1 = $this->refreshProject($proj1, $user1);
$this->assertTrue($proj1->isUserMember($user1->getPHID()));
$proj1 = $this->refreshProject($proj1, $user1, false, true);
$this->assertTrue($proj1->isUserMember($user1->getPHID()));
$this->assertFalse($proj1->isUserWatcher($user1->getPHID()));
$proj1 = $this->refreshProject($proj1, $user1, true, false);
$this->assertTrue($proj1->isUserMember($user1->getPHID()));
$this->assertFalse($proj1->isUserMember($user2->getPHID()));
$this->assertTrue($proj1->isUserMember($user3->getPHID()));
$proj1 = $this->refreshProject($proj1, $user1, true, true);
$this->assertTrue($proj1->isUserMember($user1->getPHID()));
$this->assertFalse($proj1->isUserMember($user2->getPHID()));
$this->assertTrue($proj1->isUserMember($user3->getPHID()));
$this->assertFalse($proj1->isUserWatcher($user1->getPHID()));
$this->assertFalse($proj1->isUserWatcher($user2->getPHID()));
$this->assertTrue($proj1->isUserWatcher($user3->getPHID()));
}
public function testEditProject() { public function testEditProject() {
$user = $this->createUser(); $user = $this->createUser();
$user->save(); $user->save();
@ -210,11 +253,13 @@ final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase {
private function refreshProject( private function refreshProject(
PhabricatorProject $project, PhabricatorProject $project,
PhabricatorUser $viewer, PhabricatorUser $viewer,
$need_members = false) { $need_members = false,
$need_watchers = false) {
$results = id(new PhabricatorProjectQuery()) $results = id(new PhabricatorProjectQuery())
->setViewer($viewer) ->setViewer($viewer)
->needMembers($need_members) ->needMembers($need_members)
->needWatchers($need_watchers)
->withIDs(array($project->getID())) ->withIDs(array($project->getID()))
->execute(); ->execute();
@ -255,19 +300,54 @@ final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase {
private function joinProject( private function joinProject(
PhabricatorProject $project, PhabricatorProject $project,
PhabricatorUser $user) { PhabricatorUser $user) {
$this->joinOrLeaveProject($project, $user, '+'); return $this->joinOrLeaveProject($project, $user, '+');
} }
private function leaveProject( private function leaveProject(
PhabricatorProject $project, PhabricatorProject $project,
PhabricatorUser $user) { PhabricatorUser $user) {
$this->joinOrLeaveProject($project, $user, '-'); return $this->joinOrLeaveProject($project, $user, '-');
}
private function watchProject(
PhabricatorProject $project,
PhabricatorUser $user) {
return $this->watchOrUnwatchProject($project, $user, '+');
}
private function unwatchProject(
PhabricatorProject $project,
PhabricatorUser $user) {
return $this->watchOrUnwatchProject($project, $user, '-');
} }
private function joinOrLeaveProject( private function joinOrLeaveProject(
PhabricatorProject $project, PhabricatorProject $project,
PhabricatorUser $user, PhabricatorUser $user,
$operation) { $operation) {
return $this->applyProjectEdgeTransaction(
$project,
$user,
$operation,
PhabricatorProjectProjectHasMemberEdgeType::EDGECONST);
}
private function watchOrUnwatchProject(
PhabricatorProject $project,
PhabricatorUser $user,
$operation) {
return $this->applyProjectEdgeTransaction(
$project,
$user,
$operation,
PhabricatorObjectHasWatcherEdgeType::EDGECONST);
}
private function applyProjectEdgeTransaction(
PhabricatorProject $project,
PhabricatorUser $user,
$operation,
$edge_type) {
$spec = array( $spec = array(
$operation => array($user->getPHID() => $user->getPHID()), $operation => array($user->getPHID() => $user->getPHID()),
@ -276,9 +356,7 @@ final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase {
$xactions = array(); $xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction()) $xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue( ->setMetadataValue('edge:type', $edge_type)
'edge:type',
PhabricatorProjectProjectHasMemberEdgeType::EDGECONST)
->setNewValue($spec); ->setNewValue($spec);
$editor = id(new PhabricatorProjectTransactionEditor()) $editor = id(new PhabricatorProjectTransactionEditor())
@ -286,6 +364,9 @@ final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase {
->setContentSource(PhabricatorContentSource::newConsoleSource()) ->setContentSource(PhabricatorContentSource::newConsoleSource())
->setContinueOnNoEffect(true) ->setContinueOnNoEffect(true)
->applyTransactions($project, $xactions); ->applyTransactions($project, $xactions);
return $project;
} }
} }

View file

@ -126,59 +126,57 @@ final class PhabricatorProjectQuery
} }
protected function loadPage() { protected function loadPage() {
$table = new PhabricatorProject(); return $this->loadStandardPage($this->newResultObject());
$data = $this->loadStandardPageRows($table); }
$projects = $table->loadAllFromArray($data);
if ($projects) { protected function willFilterPage(array $projects) {
$viewer_phid = $this->getViewer()->getPHID(); $viewer_phid = $this->getViewer()->getPHID();
$project_phids = mpull($projects, 'getPHID'); $project_phids = mpull($projects, 'getPHID');
$member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
$watcher_type = PhabricatorObjectHasWatcherEdgeType::EDGECONST; $watcher_type = PhabricatorObjectHasWatcherEdgeType::EDGECONST;
$types = array();
$types[] = $member_type;
if ($this->needWatchers) {
$types[] = $watcher_type;
}
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs($project_phids)
->withEdgeTypes($types);
// If we only need to know if the viewer is a member, we can restrict
// the query to just their PHID.
if (!$this->needMembers && !$this->needWatchers) {
$edge_query->withDestinationPHIDs(array($viewer_phid));
}
$edge_query->execute();
foreach ($projects as $project) {
$project_phid = $project->getPHID();
$member_phids = $edge_query->getDestinationPHIDs(
array($project_phid),
array($member_type));
$project->setIsUserMember(
$viewer_phid,
in_array($viewer_phid, $member_phids));
$need_edge_types = array();
if ($this->needMembers) { if ($this->needMembers) {
$need_edge_types[] = $member_type; $project->attachMemberPHIDs($member_phids);
} else {
foreach ($data as $row) {
$projects[$row['id']]->setIsUserMember(
$viewer_phid,
($row['viewerIsMember'] !== null));
}
} }
if ($this->needWatchers) { if ($this->needWatchers) {
$need_edge_types[] = $watcher_type; $watcher_phids = $edge_query->getDestinationPHIDs(
} array($project_phid),
array($watcher_type));
if ($need_edge_types) { $project->attachWatcherPHIDs($watcher_phids);
$edges = id(new PhabricatorEdgeQuery()) $project->setIsUserWatcher(
->withSourcePHIDs($project_phids) $viewer_phid,
->withEdgeTypes($need_edge_types) in_array($viewer_phid, $watcher_phids));
->execute();
if ($this->needMembers) {
foreach ($projects as $project) {
$phid = $project->getPHID();
$project->attachMemberPHIDs(
array_keys($edges[$phid][$member_type]));
$project->setIsUserMember(
$viewer_phid,
isset($edges[$phid][$member_type][$viewer_phid]));
}
}
if ($this->needWatchers) {
foreach ($projects as $project) {
$phid = $project->getPHID();
$project->attachWatcherPHIDs(
array_keys($edges[$phid][$watcher_type]));
$project->setIsUserWatcher(
$viewer_phid,
isset($edges[$phid][$watcher_type][$viewer_phid]));
}
}
} }
} }
@ -231,20 +229,6 @@ final class PhabricatorProjectQuery
return $projects; return $projects;
} }
protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) {
$select = parent::buildSelectClauseParts($conn);
// 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.
if (!$this->needMembers) {
$select[] = 'vm.dst viewerIsMember';
}
return $select;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn); $where = parent::buildWhereClauseParts($conn);
@ -336,15 +320,6 @@ final class PhabricatorProjectQuery
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn); $joins = parent::buildJoinClauseParts($conn);
if (!$this->needMembers !== null) {
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T vm ON vm.src = p.phid AND vm.type = %d AND vm.dst = %s',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhabricatorProjectProjectHasMemberEdgeType::EDGECONST,
$this->getViewer()->getPHID());
}
if ($this->memberPHIDs !== null) { if ($this->memberPHIDs !== null) {
$joins[] = qsprintf( $joins[] = qsprintf(
$conn, $conn,