@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+134 -78
+2 -2
src/__phutil_library_map__.php
··· 2834 2834 'PhabricatorProjectConfigOptions' => 'applications/project/config/PhabricatorProjectConfigOptions.php', 2835 2835 'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php', 2836 2836 'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php', 2837 + 'PhabricatorProjectCoreTestCase' => 'applications/project/__tests__/PhabricatorProjectCoreTestCase.php', 2837 2838 'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php', 2838 2839 'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php', 2839 2840 'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php', ··· 2843 2844 'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php', 2844 2845 'PhabricatorProjectEditDetailsController' => 'applications/project/controller/PhabricatorProjectEditDetailsController.php', 2845 2846 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', 2846 - 'PhabricatorProjectEditorTestCase' => 'applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php', 2847 2847 'PhabricatorProjectFeedController' => 'applications/project/controller/PhabricatorProjectFeedController.php', 2848 2848 'PhabricatorProjectFulltextEngine' => 'applications/project/search/PhabricatorProjectFulltextEngine.php', 2849 2849 'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php', ··· 7168 7168 'PhabricatorStandardCustomFieldInterface', 7169 7169 ), 7170 7170 'PhabricatorProjectController' => 'PhabricatorController', 7171 + 'PhabricatorProjectCoreTestCase' => 'PhabricatorTestCase', 7171 7172 'PhabricatorProjectCustomField' => 'PhabricatorCustomField', 7172 7173 'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 7173 7174 'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage', ··· 7177 7178 'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField', 7178 7179 'PhabricatorProjectEditDetailsController' => 'PhabricatorProjectController', 7179 7180 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', 7180 - 'PhabricatorProjectEditorTestCase' => 'PhabricatorTestCase', 7181 7181 'PhabricatorProjectFeedController' => 'PhabricatorProjectController', 7182 7182 'PhabricatorProjectFulltextEngine' => 'PhabricatorFulltextEngine', 7183 7183 'PhabricatorProjectHeraldAction' => 'HeraldAction',
+88 -7
src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
··· 1 1 <?php 2 2 3 - final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase { 3 + final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { 4 4 5 5 protected function getPhabricatorTestCaseConfiguration() { 6 6 return array( ··· 37 37 38 38 $this->assertTrue((bool)$this->refreshProject($proj, $user)); 39 39 $this->assertFalse((bool)$this->refreshProject($proj, $user2)); 40 + } 41 + 42 + public function testIsViewerMemberOrWatcher() { 43 + $user1 = $this->createUser() 44 + ->save(); 45 + 46 + $user2 = $this->createUser() 47 + ->save(); 48 + 49 + $user3 = $this->createUser() 50 + ->save(); 51 + 52 + $proj1 = $this->createProject($user1); 53 + $proj1 = $this->refreshProject($proj1, $user1); 54 + 55 + $this->joinProject($proj1, $user1); 56 + $this->joinProject($proj1, $user3); 57 + $this->watchProject($proj1, $user3); 58 + 59 + $proj1 = $this->refreshProject($proj1, $user1); 60 + 61 + $this->assertTrue($proj1->isUserMember($user1->getPHID())); 62 + 63 + $proj1 = $this->refreshProject($proj1, $user1, false, true); 64 + 65 + $this->assertTrue($proj1->isUserMember($user1->getPHID())); 66 + $this->assertFalse($proj1->isUserWatcher($user1->getPHID())); 67 + 68 + $proj1 = $this->refreshProject($proj1, $user1, true, false); 69 + 70 + $this->assertTrue($proj1->isUserMember($user1->getPHID())); 71 + $this->assertFalse($proj1->isUserMember($user2->getPHID())); 72 + $this->assertTrue($proj1->isUserMember($user3->getPHID())); 73 + 74 + $proj1 = $this->refreshProject($proj1, $user1, true, true); 75 + 76 + $this->assertTrue($proj1->isUserMember($user1->getPHID())); 77 + $this->assertFalse($proj1->isUserMember($user2->getPHID())); 78 + $this->assertTrue($proj1->isUserMember($user3->getPHID())); 79 + 80 + $this->assertFalse($proj1->isUserWatcher($user1->getPHID())); 81 + $this->assertFalse($proj1->isUserWatcher($user2->getPHID())); 82 + $this->assertTrue($proj1->isUserWatcher($user3->getPHID())); 40 83 } 41 84 42 85 public function testEditProject() { ··· 210 253 private function refreshProject( 211 254 PhabricatorProject $project, 212 255 PhabricatorUser $viewer, 213 - $need_members = false) { 256 + $need_members = false, 257 + $need_watchers = false) { 214 258 215 259 $results = id(new PhabricatorProjectQuery()) 216 260 ->setViewer($viewer) 217 261 ->needMembers($need_members) 262 + ->needWatchers($need_watchers) 218 263 ->withIDs(array($project->getID())) 219 264 ->execute(); 220 265 ··· 255 300 private function joinProject( 256 301 PhabricatorProject $project, 257 302 PhabricatorUser $user) { 258 - $this->joinOrLeaveProject($project, $user, '+'); 303 + return $this->joinOrLeaveProject($project, $user, '+'); 259 304 } 260 305 261 306 private function leaveProject( 262 307 PhabricatorProject $project, 263 308 PhabricatorUser $user) { 264 - $this->joinOrLeaveProject($project, $user, '-'); 309 + return $this->joinOrLeaveProject($project, $user, '-'); 310 + } 311 + 312 + private function watchProject( 313 + PhabricatorProject $project, 314 + PhabricatorUser $user) { 315 + return $this->watchOrUnwatchProject($project, $user, '+'); 316 + } 317 + 318 + private function unwatchProject( 319 + PhabricatorProject $project, 320 + PhabricatorUser $user) { 321 + return $this->watchOrUnwatchProject($project, $user, '-'); 265 322 } 266 323 267 324 private function joinOrLeaveProject( 268 325 PhabricatorProject $project, 269 326 PhabricatorUser $user, 270 327 $operation) { 328 + return $this->applyProjectEdgeTransaction( 329 + $project, 330 + $user, 331 + $operation, 332 + PhabricatorProjectProjectHasMemberEdgeType::EDGECONST); 333 + } 334 + 335 + private function watchOrUnwatchProject( 336 + PhabricatorProject $project, 337 + PhabricatorUser $user, 338 + $operation) { 339 + return $this->applyProjectEdgeTransaction( 340 + $project, 341 + $user, 342 + $operation, 343 + PhabricatorObjectHasWatcherEdgeType::EDGECONST); 344 + } 345 + 346 + private function applyProjectEdgeTransaction( 347 + PhabricatorProject $project, 348 + PhabricatorUser $user, 349 + $operation, 350 + $edge_type) { 271 351 272 352 $spec = array( 273 353 $operation => array($user->getPHID() => $user->getPHID()), ··· 276 356 $xactions = array(); 277 357 $xactions[] = id(new PhabricatorProjectTransaction()) 278 358 ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 279 - ->setMetadataValue( 280 - 'edge:type', 281 - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST) 359 + ->setMetadataValue('edge:type', $edge_type) 282 360 ->setNewValue($spec); 283 361 284 362 $editor = id(new PhabricatorProjectTransactionEditor()) ··· 286 364 ->setContentSource(PhabricatorContentSource::newConsoleSource()) 287 365 ->setContinueOnNoEffect(true) 288 366 ->applyTransactions($project, $xactions); 367 + 368 + return $project; 289 369 } 370 + 290 371 291 372 }
+44 -69
src/applications/project/query/PhabricatorProjectQuery.php
··· 126 126 } 127 127 128 128 protected function loadPage() { 129 - $table = new PhabricatorProject(); 130 - $data = $this->loadStandardPageRows($table); 131 - $projects = $table->loadAllFromArray($data); 129 + return $this->loadStandardPage($this->newResultObject()); 130 + } 131 + 132 + protected function willFilterPage(array $projects) { 133 + $viewer_phid = $this->getViewer()->getPHID(); 134 + $project_phids = mpull($projects, 'getPHID'); 135 + 136 + $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; 137 + $watcher_type = PhabricatorObjectHasWatcherEdgeType::EDGECONST; 138 + 139 + $types = array(); 140 + $types[] = $member_type; 141 + if ($this->needWatchers) { 142 + $types[] = $watcher_type; 143 + } 144 + 145 + $edge_query = id(new PhabricatorEdgeQuery()) 146 + ->withSourcePHIDs($project_phids) 147 + ->withEdgeTypes($types); 148 + 149 + // If we only need to know if the viewer is a member, we can restrict 150 + // the query to just their PHID. 151 + if (!$this->needMembers && !$this->needWatchers) { 152 + $edge_query->withDestinationPHIDs(array($viewer_phid)); 153 + } 154 + 155 + $edge_query->execute(); 156 + 157 + foreach ($projects as $project) { 158 + $project_phid = $project->getPHID(); 132 159 133 - if ($projects) { 134 - $viewer_phid = $this->getViewer()->getPHID(); 135 - $project_phids = mpull($projects, 'getPHID'); 160 + $member_phids = $edge_query->getDestinationPHIDs( 161 + array($project_phid), 162 + array($member_type)); 136 163 137 - $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; 138 - $watcher_type = PhabricatorObjectHasWatcherEdgeType::EDGECONST; 164 + $project->setIsUserMember( 165 + $viewer_phid, 166 + in_array($viewer_phid, $member_phids)); 139 167 140 - $need_edge_types = array(); 141 168 if ($this->needMembers) { 142 - $need_edge_types[] = $member_type; 143 - } else { 144 - foreach ($data as $row) { 145 - $projects[$row['id']]->setIsUserMember( 146 - $viewer_phid, 147 - ($row['viewerIsMember'] !== null)); 148 - } 169 + $project->attachMemberPHIDs($member_phids); 149 170 } 150 171 151 172 if ($this->needWatchers) { 152 - $need_edge_types[] = $watcher_type; 153 - } 154 - 155 - if ($need_edge_types) { 156 - $edges = id(new PhabricatorEdgeQuery()) 157 - ->withSourcePHIDs($project_phids) 158 - ->withEdgeTypes($need_edge_types) 159 - ->execute(); 160 - 161 - if ($this->needMembers) { 162 - foreach ($projects as $project) { 163 - $phid = $project->getPHID(); 164 - $project->attachMemberPHIDs( 165 - array_keys($edges[$phid][$member_type])); 166 - $project->setIsUserMember( 167 - $viewer_phid, 168 - isset($edges[$phid][$member_type][$viewer_phid])); 169 - } 170 - } 171 - 172 - if ($this->needWatchers) { 173 - foreach ($projects as $project) { 174 - $phid = $project->getPHID(); 175 - $project->attachWatcherPHIDs( 176 - array_keys($edges[$phid][$watcher_type])); 177 - $project->setIsUserWatcher( 178 - $viewer_phid, 179 - isset($edges[$phid][$watcher_type][$viewer_phid])); 180 - } 181 - } 173 + $watcher_phids = $edge_query->getDestinationPHIDs( 174 + array($project_phid), 175 + array($watcher_type)); 176 + $project->attachWatcherPHIDs($watcher_phids); 177 + $project->setIsUserWatcher( 178 + $viewer_phid, 179 + in_array($viewer_phid, $watcher_phids)); 182 180 } 183 181 } 184 182 ··· 229 227 } 230 228 231 229 return $projects; 232 - } 233 - 234 - protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) { 235 - $select = parent::buildSelectClauseParts($conn); 236 - 237 - // NOTE: Because visibility checks for projects depend on whether or not 238 - // the user is a project member, we always load their membership. If we're 239 - // loading all members anyway we can piggyback on that; otherwise we 240 - // do an explicit join. 241 - if (!$this->needMembers) { 242 - $select[] = 'vm.dst viewerIsMember'; 243 - } 244 - 245 - return $select; 246 230 } 247 231 248 232 protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { ··· 335 319 336 320 protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { 337 321 $joins = parent::buildJoinClauseParts($conn); 338 - 339 - if (!$this->needMembers !== null) { 340 - $joins[] = qsprintf( 341 - $conn, 342 - 'LEFT JOIN %T vm ON vm.src = p.phid AND vm.type = %d AND vm.dst = %s', 343 - PhabricatorEdgeConfig::TABLE_NAME_EDGE, 344 - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST, 345 - $this->getViewer()->getPHID()); 346 - } 347 322 348 323 if ($this->memberPHIDs !== null) { 349 324 $joins[] = qsprintf(