Implement child/descendant query rules in Projects
Summary: Ref T10010. This adds infrastructure for querying projects by type, depth, parent and ancestor. I needed to revise the "extended policy check" cycle detection rules. When, e.g., querying a grandchild, they incorrectly detected a cycle because both the child and grandchild needed to check the policy of the grandparent. Instead, simplify it to just do a basic runaway calldepth check. There are many other safety mechanisms to make it so this can't ever occur. (Cycle detection does have existing test coverage, and those tests still pass, it just takes a little longer to detect the cycle internally.) There is still no way to create subprojects in the UI. Test Plan: Added and executed unit tests. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14862
This commit is contained in:
@@ -11,6 +11,11 @@ final class PhabricatorProjectQuery
|
||||
private $nameTokens;
|
||||
private $icons;
|
||||
private $colors;
|
||||
private $ancestorPHIDs;
|
||||
private $parentPHIDs;
|
||||
private $isMilestone;
|
||||
private $minDepth;
|
||||
private $maxDepth;
|
||||
|
||||
private $status = 'status-any';
|
||||
const STATUS_ANY = 'status-any';
|
||||
@@ -69,6 +74,27 @@ final class PhabricatorProjectQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withParentProjectPHIDs($parent_phids) {
|
||||
$this->parentPHIDs = $parent_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withAncestorProjectPHIDs($ancestor_phids) {
|
||||
$this->ancestorPHIDs = $ancestor_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withIsMilestone($is_milestone) {
|
||||
$this->isMilestone = $is_milestone;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withDepthBetween($min, $max) {
|
||||
$this->minDepth = $min;
|
||||
$this->maxDepth = $max;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needMembers($need_members) {
|
||||
$this->needMembers = $need_members;
|
||||
return $this;
|
||||
@@ -337,6 +363,65 @@ final class PhabricatorProjectQuery
|
||||
$this->colors);
|
||||
}
|
||||
|
||||
if ($this->parentPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'parentProjectPHID IN (%Ls)',
|
||||
$this->parentPHIDs);
|
||||
}
|
||||
|
||||
if ($this->ancestorPHIDs !== null) {
|
||||
$ancestor_paths = queryfx_all(
|
||||
$conn,
|
||||
'SELECT projectPath, projectDepth FROM %T WHERE phid IN (%Ls)',
|
||||
id(new PhabricatorProject())->getTableName(),
|
||||
$this->ancestorPHIDs);
|
||||
if (!$ancestor_paths) {
|
||||
throw new PhabricatorEmptyQueryException();
|
||||
}
|
||||
|
||||
$sql = array();
|
||||
foreach ($ancestor_paths as $ancestor_path) {
|
||||
$sql[] = qsprintf(
|
||||
$conn,
|
||||
'(projectPath LIKE %> AND projectDepth > %d)',
|
||||
$ancestor_path['projectPath'],
|
||||
$ancestor_path['projectDepth']);
|
||||
}
|
||||
|
||||
$where[] = '('.implode(' OR ', $sql).')';
|
||||
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'parentProjectPHID IS NOT NULL');
|
||||
}
|
||||
|
||||
if ($this->isMilestone !== null) {
|
||||
if ($this->isMilestone) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'milestoneNumber IS NOT NULL');
|
||||
} else {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'milestoneNumber IS NULL');
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->minDepth !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'projectDepth >= %d',
|
||||
$this->minDepth);
|
||||
}
|
||||
|
||||
if ($this->maxDepth !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'projectDepth <= %d',
|
||||
$this->maxDepth);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user