Page MenuHomePhabricator

D14863.id.diff
No OneTemporary

D14863.id.diff

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

Mime Type
text/plain
Expires
Mon, May 20, 1:30 PM (3 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6302931
Default Alt Text
D14863.id.diff (16 KB)

Event Timeline