Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15335535
D14863.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
D14863.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2855,6 +2855,7 @@
'PhabricatorProjectLogicalOrNotDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php',
'PhabricatorProjectLogicalUserDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalUserDatasource.php',
'PhabricatorProjectLogicalViewerDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php',
+ 'PhabricatorProjectMaterializedMemberEdgeType' => 'applications/project/edge/PhabricatorProjectMaterializedMemberEdgeType.php',
'PhabricatorProjectMemberOfProjectEdgeType' => 'applications/project/edge/PhabricatorProjectMemberOfProjectEdgeType.php',
'PhabricatorProjectMembersDatasource' => 'applications/project/typeahead/PhabricatorProjectMembersDatasource.php',
'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php',
@@ -2889,6 +2890,7 @@
'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php',
'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php',
'PhabricatorProjectsFulltextEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php',
+ 'PhabricatorProjectsMembershipIndexEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php',
'PhabricatorProjectsPolicyRule' => 'applications/project/policyrule/PhabricatorProjectsPolicyRule.php',
'PhabricatorProjectsSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineAttachment.php',
'PhabricatorProjectsSearchEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineExtension.php',
@@ -7189,6 +7191,7 @@
'PhabricatorProjectLogicalOrNotDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectLogicalUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectLogicalViewerDatasource' => 'PhabricatorTypeaheadDatasource',
+ 'PhabricatorProjectMaterializedMemberEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectMemberOfProjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorProjectMembersDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController',
@@ -7226,6 +7229,7 @@
'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField',
'PhabricatorProjectsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
+ 'PhabricatorProjectsMembershipIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorProjectsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorProjectsSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
--- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
+++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php
@@ -187,6 +187,68 @@
$this->assertEqual(2, count($projects));
}
+ public function testMemberMaterialization() {
+ $material_type = PhabricatorProjectMaterializedMemberEdgeType::EDGECONST;
+
+ $user = $this->createUser();
+ $user->save();
+
+ $parent = $this->createProject($user);
+ $child = $this->createProject($user, $parent);
+
+ $this->joinProject($child, $user);
+
+ $parent_material = PhabricatorEdgeQuery::loadDestinationPHIDs(
+ $parent->getPHID(),
+ $material_type);
+
+ $this->assertEqual(
+ array($user->getPHID()),
+ $parent_material);
+ }
+
+ public function testMilestones() {
+ $user = $this->createUser();
+ $user->save();
+
+ $parent = $this->createProject($user);
+
+ $m1 = $this->createProject($user, $parent, true);
+ $m2 = $this->createProject($user, $parent, true);
+ $m3 = $this->createProject($user, $parent, true);
+
+ $this->assertEqual(1, $m1->getMilestoneNumber());
+ $this->assertEqual(2, $m2->getMilestoneNumber());
+ $this->assertEqual(3, $m3->getMilestoneNumber());
+ }
+
+ public function testMilestoneMembership() {
+ $user = $this->createUser();
+ $user->save();
+
+ $parent = $this->createProject($user);
+ $milestone = $this->createProject($user, $parent, true);
+
+ $this->joinProject($parent, $user);
+
+ $milestone = id(new PhabricatorProjectQuery())
+ ->setViewer($user)
+ ->withPHIDs(array($milestone->getPHID()))
+ ->executeOne();
+
+ $this->assertTrue($milestone->isUserMember($user->getPHID()));
+
+ $milestone = id(new PhabricatorProjectQuery())
+ ->setViewer($user)
+ ->withPHIDs(array($milestone->getPHID()))
+ ->needMembers(true)
+ ->executeOne();
+
+ $this->assertEqual(
+ array($user->getPHID()),
+ $milestone->getMemberPHIDs());
+ }
+
public function testParentProject() {
$user = $this->createUser();
$user->save();
@@ -396,10 +458,12 @@
private function createProject(
PhabricatorUser $user,
- PhabricatorProject $parent = null) {
+ PhabricatorProject $parent = null,
+ $is_milestone = false) {
$project = PhabricatorProject::initializeNewProject($user);
+
$name = pht('Test Project %d', mt_rand());
$xactions = array();
@@ -414,6 +478,12 @@
->setNewValue($parent->getPHID());
}
+ if ($is_milestone) {
+ $xactions[] = id(new PhabricatorProjectTransaction())
+ ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE)
+ ->setNewValue(true);
+ }
+
$this->applyTransactions($project, $user, $xactions);
return $project;
diff --git a/src/applications/project/edge/PhabricatorProjectMaterializedMemberEdgeType.php b/src/applications/project/edge/PhabricatorProjectMaterializedMemberEdgeType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/edge/PhabricatorProjectMaterializedMemberEdgeType.php
@@ -0,0 +1,8 @@
+<?php
+
+final class PhabricatorProjectMaterializedMemberEdgeType
+ extends PhabricatorEdgeType {
+
+ const EDGECONST = 60;
+
+}
diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php
--- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php
+++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php
@@ -27,6 +27,7 @@
$types[] = PhabricatorProjectTransaction::TYPE_COLOR;
$types[] = PhabricatorProjectTransaction::TYPE_LOCKED;
$types[] = PhabricatorProjectTransaction::TYPE_PARENT;
+ $types[] = PhabricatorProjectTransaction::TYPE_MILESTONE;
return $types;
}
@@ -54,6 +55,7 @@
case PhabricatorProjectTransaction::TYPE_LOCKED:
return (int)$object->getIsMembershipLocked();
case PhabricatorProjectTransaction::TYPE_PARENT:
+ case PhabricatorProjectTransaction::TYPE_MILESTONE:
return null;
}
@@ -74,6 +76,20 @@
case PhabricatorProjectTransaction::TYPE_LOCKED:
case PhabricatorProjectTransaction::TYPE_PARENT:
return $xaction->getNewValue();
+ case PhabricatorProjectTransaction::TYPE_MILESTONE:
+ $current = queryfx_one(
+ $object->establishConnection('w'),
+ 'SELECT MAX(milestoneNumber) n
+ FROM %T
+ WHERE parentProjectPHID = %s',
+ $object->getTableName(),
+ $object->getParentProject()->getPHID());
+ if (!$current) {
+ $number = 1;
+ } else {
+ $number = (int)$current['n'] + 1;
+ }
+ return $number;
}
return parent::getCustomTransactionNewValue($object, $xaction);
@@ -109,6 +125,9 @@
case PhabricatorProjectTransaction::TYPE_PARENT:
$object->setParentProjectPHID($xaction->getNewValue());
return;
+ case PhabricatorProjectTransaction::TYPE_MILESTONE:
+ $object->setMilestoneNumber($xaction->getNewValue());
+ return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
@@ -161,6 +180,7 @@
case PhabricatorProjectTransaction::TYPE_COLOR:
case PhabricatorProjectTransaction::TYPE_LOCKED:
case PhabricatorProjectTransaction::TYPE_PARENT:
+ case PhabricatorProjectTransaction::TYPE_MILESTONE:
return;
}
@@ -590,4 +610,34 @@
->setProjectPHID($object->getPHID())
->save();
}
+
+
+ protected function applyFinalEffects(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ $materialize = false;
+ foreach ($xactions as $xaction) {
+ switch ($xaction->getTransactionType()) {
+ case PhabricatorTransactions::TYPE_EDGE:
+ switch ($xaction->getMetadataValue('edge:type')) {
+ case PhabricatorProjectProjectHasMemberEdgeType::EDGECONST:
+ $materialize = true;
+ break;
+ }
+ break;
+ case PhabricatorProjectTransaction::TYPE_PARENT:
+ $materialize = true;
+ break;
+ }
+ }
+
+ if ($materialize) {
+ id(new PhabricatorProjectsMembershipIndexEngineExtension())
+ ->rematerialize($object);
+ }
+
+ return parent::applyFinalEffects($object, $xactions);
+ }
+
}
diff --git a/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php b/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php
@@ -0,0 +1,98 @@
+<?php
+
+final class PhabricatorProjectsMembershipIndexEngineExtension
+ extends PhabricatorIndexEngineExtension {
+
+ const EXTENSIONKEY = 'project.members';
+
+ public function getExtensionName() {
+ return pht('Project Members');
+ }
+
+ public function shouldIndexObject($object) {
+ if (!($object instanceof PhabricatorProject)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function indexObject(
+ PhabricatorIndexEngine $engine,
+ $object) {
+
+ $this->rematerialize($object);
+ }
+
+ public function rematerialize(PhabricatorProject $project) {
+ $materialize = $project->getAncestorProjects();
+ array_unshift($materialize, $project);
+
+ foreach ($materialize as $project) {
+ $this->materializeProject($project);
+ }
+ }
+
+ private function materializeProject(PhabricatorProject $project) {
+ if ($project->isMilestone()) {
+ return;
+ }
+
+ $material_type = PhabricatorProjectMaterializedMemberEdgeType::EDGECONST;
+ $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
+
+ $project_phid = $project->getPHID();
+
+ $descendants = id(new PhabricatorProjectQuery())
+ ->setViewer($this->getViewer())
+ ->withAncestorProjectPHIDs(array($project->getPHID()))
+ ->withIsMilestone(false)
+ ->withHasSubprojects(false)
+ ->execute();
+ $descendant_phids = mpull($descendants, 'getPHID');
+
+ if ($descendant_phids) {
+ $source_phids = $descendant_phids;
+ $has_subprojects = true;
+ } else {
+ $source_phids = array($project->getPHID());
+ $has_subprojects = false;
+ }
+
+ $conn_w = $project->establishConnection('w');
+
+ $project->openTransaction();
+
+ // Delete any existing materialized member edges.
+ queryfx(
+ $conn_w,
+ 'DELETE FROM %T WHERE src = %s AND type = %s',
+ PhabricatorEdgeConfig::TABLE_NAME_EDGE,
+ $project_phid,
+ $material_type);
+
+ // Copy current member edges to create new materialized edges.
+ queryfx(
+ $conn_w,
+ 'INSERT IGNORE INTO %T (src, type, dst, dateCreated, seq)
+ SELECT %s, %d, dst, dateCreated, seq FROM %T
+ WHERE src IN (%Ls) AND type = %d',
+ PhabricatorEdgeConfig::TABLE_NAME_EDGE,
+ $project_phid,
+ $material_type,
+ PhabricatorEdgeConfig::TABLE_NAME_EDGE,
+ $source_phids,
+ $member_type);
+
+ // Update the hasSubprojects flag.
+ queryfx(
+ $conn_w,
+ 'UPDATE %T SET hasSubprojects = %d WHERE id = %d',
+ $project->getTableName(),
+ (int)$has_subprojects,
+ $project->getID());
+
+ $project->saveTransaction();
+ }
+
+}
diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php
--- a/src/applications/project/query/PhabricatorProjectQuery.php
+++ b/src/applications/project/query/PhabricatorProjectQuery.php
@@ -14,6 +14,7 @@
private $ancestorPHIDs;
private $parentPHIDs;
private $isMilestone;
+ private $hasSubprojects;
private $minDepth;
private $maxDepth;
@@ -89,6 +90,15 @@
return $this;
}
+ public function withHasSubprojects($has_subprojects) {
+ $this->hasSubprojects = $has_subprojects;
+ return $this;
+ }
+
+ public function getProperty() {
+ return $this->property;
+ }
+
public function withDepthBetween($min, $max) {
$this->minDepth = $min;
$this->maxDepth = $max;
@@ -156,12 +166,8 @@
}
protected function willFilterPage(array $projects) {
- $project_phids = array();
$ancestor_paths = array();
-
foreach ($projects as $project) {
- $project_phids[] = $project->getPHID();
-
foreach ($project->getAncestorProjectPaths() as $path) {
$ancestor_paths[$path] = $path;
}
@@ -178,7 +184,6 @@
$projects = $this->linkProjectGraph($projects, $ancestors);
$viewer_phid = $this->getViewer()->getPHID();
- $project_phids = mpull($projects, 'getPHID');
$member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
$watcher_type = PhabricatorObjectHasWatcherEdgeType::EDGECONST;
@@ -189,8 +194,18 @@
$types[] = $watcher_type;
}
+ $all_sources = array();
+ foreach ($projects as $project) {
+ if ($project->isMilestone()) {
+ $phid = $project->getParentProjectPHID();
+ } else {
+ $phid = $project->getPHID();
+ }
+ $all_sources[$phid] = $phid;
+ }
+
$edge_query = id(new PhabricatorEdgeQuery())
- ->withSourcePHIDs($project_phids)
+ ->withSourcePHIDs($all_sources)
->withEdgeTypes($types);
// If we only need to know if the viewer is a member, we can restrict
@@ -205,8 +220,14 @@
foreach ($projects as $project) {
$project_phid = $project->getPHID();
+ if ($project->isMilestone()) {
+ $source_phids = array($project->getParentProjectPHID());
+ } else {
+ $source_phids = array($project_phid);
+ }
+
$member_phids = $edge_query->getDestinationPHIDs(
- array($project_phid),
+ $source_phids,
array($member_type));
if (in_array($viewer_phid, $member_phids)) {
@@ -219,7 +240,7 @@
if ($this->needWatchers) {
$watcher_phids = $edge_query->getDestinationPHIDs(
- array($project_phid),
+ $source_phids,
array($watcher_type));
$project->attachWatcherPHIDs($watcher_phids);
$project->setIsUserWatcher(
@@ -408,6 +429,13 @@
}
}
+ if ($this->hasSubprojects !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'hasSubprojects = %d',
+ (int)$this->hasSubprojects);
+ }
+
if ($this->minDepth !== null) {
$where[] = qsprintf(
$conn,
diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php
--- a/src/applications/project/storage/PhabricatorProjectTransaction.php
+++ b/src/applications/project/storage/PhabricatorProjectTransaction.php
@@ -11,6 +11,7 @@
const TYPE_COLOR = 'project:color';
const TYPE_LOCKED = 'project:locked';
const TYPE_PARENT = 'project:parent';
+ const TYPE_MILESTONE = 'project:milestone';
// NOTE: This is deprecated, members are just a normal edge now.
const TYPE_MEMBERS = 'project:members';
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Mar 9, 4:11 PM (2 d, 1 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7384923
Default Alt Text
D14863.diff (16 KB)
Attached To
Mode
D14863: Materialize parent project memberships
Attached
Detach File
Event Timeline
Log In to Comment