1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-29 18:22:41 +01:00

Add an "only()" edge logic constraint, meaning "only the other constraints, exactly"

Summary:
See PHI57. For example, a query for "ios, only()" finds tags tasked with iOS, exactly, and no other tags.

I called this "only()" instead of "exact()" because we use the term/function "Exact" elsewhere with a different meaning, e.g. in Differential.

Test Plan:
Basic query for a tag:

{F5168857}

Same query with "only", finds tasks tagged with only that tag:

{F5168858}

Reviewers: chad

Reviewed By: chad

Differential Revision: https://secure.phabricator.com/D18543
This commit is contained in:
epriestley 2017-09-06 10:02:13 -07:00
parent 2abbb59cb4
commit 395a2ed6d1
6 changed files with 146 additions and 1 deletions

View file

@ -3686,6 +3686,7 @@ phutil_register_library_map(array(
'PhabricatorProjectLockTransaction' => 'applications/project/xaction/PhabricatorProjectLockTransaction.php',
'PhabricatorProjectLogicalAncestorDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php',
'PhabricatorProjectLogicalDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalDatasource.php',
'PhabricatorProjectLogicalOnlyDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOnlyDatasource.php',
'PhabricatorProjectLogicalOrNotDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php',
'PhabricatorProjectLogicalUserDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalUserDatasource.php',
'PhabricatorProjectLogicalViewerDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php',
@ -9179,6 +9180,7 @@ phutil_register_library_map(array(
'PhabricatorProjectLockTransaction' => 'PhabricatorProjectTransactionType',
'PhabricatorProjectLogicalAncestorDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectLogicalDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectLogicalOnlyDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorProjectLogicalOrNotDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectLogicalUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorProjectLogicalViewerDatasource' => 'PhabricatorTypeaheadDatasource',

View file

@ -21,6 +21,7 @@ final class PhabricatorProjectLogicalDatasource
new PhabricatorProjectLogicalAncestorDatasource(),
new PhabricatorProjectLogicalOrNotDatasource(),
new PhabricatorProjectLogicalViewerDatasource(),
new PhabricatorProjectLogicalOnlyDatasource(),
new PhabricatorProjectLogicalUserDatasource(),
);
}

View file

@ -0,0 +1,76 @@
<?php
final class PhabricatorProjectLogicalOnlyDatasource
extends PhabricatorTypeaheadDatasource {
public function getBrowseTitle() {
return pht('Browse Only');
}
public function getPlaceholderText() {
return pht('Type only()...');
}
public function getDatasourceApplicationClass() {
return 'PhabricatorProjectApplication';
}
public function getDatasourceFunctions() {
return array(
'only' => array(
'name' => pht('Only Match Other Constraints'),
'summary' => pht(
'Find results with only the specified tags.'),
'description' => pht(
"This function is used with other tags, and causes the query to ".
"match only results with exactly those tags. For example, to find ".
"tasks tagged only iOS:".
"\n\n".
"> ios, only()".
"\n\n".
"This will omit results with any other project tag."),
),
);
}
public function loadResults() {
$results = array(
$this->renderOnlyFunctionToken(),
);
return $this->filterResultsAgainstTokens($results);
}
protected function evaluateFunction($function, array $argv_list) {
$results = array();
$results[] = new PhabricatorQueryConstraint(
PhabricatorQueryConstraint::OPERATOR_ONLY,
null);
return $results;
}
public function renderFunctionTokens(
$function,
array $argv_list) {
$tokens = array();
foreach ($argv_list as $argv) {
$tokens[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult(
$this->renderOnlyFunctionToken());
}
return $tokens;
}
private function renderOnlyFunctionToken() {
return $this->newFunctionResult()
->setName(pht('Only'))
->setPHID('only()')
->setIcon('fa-asterisk')
->setUnique(true)
->addAttribute(
pht('Select only results with exactly the other specified tags.'));
}
}

View file

@ -29,7 +29,7 @@ final class PhabricatorProjectLogicalViewerDatasource
"\n\n".
"This normally means //your// projects, but if you save a query ".
"using this function and send it to someone else, it will mean ".
"//their// projects when they run it (they become the currnet ".
"//their// projects when they run it (they become the current ".
"viewer). This can be useful for building dashboard panels."),
),
);

View file

@ -8,6 +8,7 @@ final class PhabricatorQueryConstraint extends Phobject {
const OPERATOR_NULL = 'null';
const OPERATOR_ANCESTOR = 'ancestor';
const OPERATOR_EMPTY = 'empty';
const OPERATOR_ONLY = 'only';
private $operator;
private $value;

View file

@ -2069,6 +2069,27 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
$op_null = PhabricatorQueryConstraint::OPERATOR_NULL;
$has_null = isset($constraints[$op_null]);
// If we're going to process an only() operator, build a list of the
// acceptable set of PHIDs first. We'll only match results which have
// no edges to any other PHIDs.
$all_phids = array();
if (isset($constraints[PhabricatorQueryConstraint::OPERATOR_ONLY])) {
foreach ($constraints as $operator => $list) {
switch ($operator) {
case PhabricatorQueryConstraint::OPERATOR_ANCESTOR:
case PhabricatorQueryConstraint::OPERATOR_AND:
case PhabricatorQueryConstraint::OPERATOR_OR:
foreach ($list as $constraint) {
$value = (array)$constraint->getValue();
foreach ($value as $v) {
$all_phids[$v] = $v;
}
}
break;
}
}
}
foreach ($constraints as $operator => $list) {
$alias = $this->getEdgeLogicTableAlias($operator, $type);
@ -2133,6 +2154,20 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
$alias,
$type);
break;
case PhabricatorQueryConstraint::OPERATOR_ONLY:
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T %T ON %Q = %T.src AND %T.type = %d
AND %T.dst NOT IN (%Ls)',
$edge_table,
$alias,
$phid_column,
$alias,
$alias,
$type,
$alias,
$all_phids);
break;
}
}
}
@ -2159,6 +2194,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
$alias = $this->getEdgeLogicTableAlias($operator, $type);
switch ($operator) {
case PhabricatorQueryConstraint::OPERATOR_NOT:
case PhabricatorQueryConstraint::OPERATOR_ONLY:
$full[] = qsprintf(
$conn,
'%T.dst IS NULL',
@ -2258,6 +2294,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
// discussion, see T12753.
return true;
case PhabricatorQueryConstraint::OPERATOR_NULL:
case PhabricatorQueryConstraint::OPERATOR_ONLY:
return true;
}
}
@ -2375,6 +2412,34 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
}
}
$op_and = PhabricatorQueryConstraint::OPERATOR_AND;
$op_or = PhabricatorQueryConstraint::OPERATOR_OR;
$op_ancestor = PhabricatorQueryConstraint::OPERATOR_ANCESTOR;
foreach ($this->edgeLogicConstraints as $type => $constraints) {
foreach ($constraints as $operator => $list) {
switch ($operator) {
case PhabricatorQueryConstraint::OPERATOR_ONLY:
if (count($list) > 1) {
throw new PhabricatorEmptyQueryException(
pht(
'This query specifies only() more than once.'));
}
$have_and = idx($constraints, $op_and);
$have_or = idx($constraints, $op_or);
$have_ancestor = idx($constraints, $op_ancestor);
if (!$have_and && !$have_or && !$have_ancestor) {
throw new PhabricatorEmptyQueryException(
pht(
'This query specifies only(), but no other constraints '.
'which it can apply to.'));
}
break;
}
}
}
$this->edgeLogicConstraintsAreValid = true;
return $this;