1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 08:52:39 +01:00

Support "Any Value" and "No Value" search constraints for datasource Custom Fields

Summary: Depends on D19126. Ref T13090. For datasource custom fields, this proxies the datasource and provides "none()" and "any()" functions to allow you to search for objects with no values or any values.

Test Plan:
  - Created a custom "Owning Group" field in Maniphest using a Projects datasource.
  - For a task with no owner assigned, searched for "none()" (hit) and "any()" (miss).
  - Assigned the task to an owning project.
  - Searched for "none()" (miss), "any()" (hit), the project it is now a member of (hit) and some random other project (miss).

Maniphest Tasks: T13090

Differential Revision: https://secure.phabricator.com/D19127
This commit is contained in:
epriestley 2018-02-22 12:23:41 -08:00
parent d0591f3680
commit 3203fd9eea
6 changed files with 249 additions and 1 deletions

View file

@ -2617,6 +2617,9 @@ phutil_register_library_map(array(
'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php',
'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php',
'PhabricatorCustomField' => 'infrastructure/customfield/field/PhabricatorCustomField.php',
'PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource' => 'infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource.php',
'PhabricatorCustomFieldApplicationSearchDatasource' => 'infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchDatasource.php',
'PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource' => 'infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource.php',
'PhabricatorCustomFieldAttachment' => 'infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php',
'PhabricatorCustomFieldConfigOptionType' => 'infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php',
'PhabricatorCustomFieldDataNotAvailableException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldDataNotAvailableException.php',
@ -4387,6 +4390,7 @@ phutil_register_library_map(array(
'PhabricatorTypeaheadInvalidTokenException' => 'applications/typeahead/exception/PhabricatorTypeaheadInvalidTokenException.php',
'PhabricatorTypeaheadModularDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php',
'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php',
'PhabricatorTypeaheadProxyDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadProxyDatasource.php',
'PhabricatorTypeaheadResult' => 'applications/typeahead/storage/PhabricatorTypeaheadResult.php',
'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php',
'PhabricatorTypeaheadTestNumbersDatasource' => 'applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php',
@ -8122,6 +8126,9 @@ phutil_register_library_map(array(
'PhabricatorCountdownViewController' => 'PhabricatorCountdownController',
'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery',
'PhabricatorCustomField' => 'Phobject',
'PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorCustomFieldApplicationSearchDatasource' => 'PhabricatorTypeaheadProxyDatasource',
'PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorCustomFieldAttachment' => 'Phobject',
'PhabricatorCustomFieldConfigOptionType' => 'PhabricatorConfigOptionType',
'PhabricatorCustomFieldDataNotAvailableException' => 'Exception',
@ -10182,6 +10189,7 @@ phutil_register_library_map(array(
'PhabricatorTypeaheadInvalidTokenException' => 'Exception',
'PhabricatorTypeaheadModularDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorTypeaheadProxyDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorTypeaheadResult' => 'Phobject',
'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorTypeaheadTestNumbersDatasource' => 'PhabricatorTypeaheadDatasource',

View file

@ -0,0 +1,58 @@
<?php
abstract class PhabricatorTypeaheadProxyDatasource
extends PhabricatorTypeaheadCompositeDatasource {
private $datasource;
public function setDatasource(PhabricatorTypeaheadDatasource $datasource) {
$this->datasource = $datasource;
$this->setParameters(
array(
'class' => get_class($datasource),
'parameters' => $datasource->getParameters(),
));
return $this;
}
public function getDatasource() {
if (!$this->datasource) {
$class = $this->getParameter('class');
$parent = 'PhabricatorTypeaheadDatasource';
if (!is_subclass_of($class, $parent)) {
throw new Exception(
pht(
'Configured datasource class "%s" must be a valid subclass of '.
'"%s".',
$class,
$parent));
}
$datasource = newv($class, array());
$datasource->setParameters($this->getParameter('parameters', array()));
$this->datasource = $datasource;
}
return $this->datasource;
}
public function getComponentDatasources() {
return array(
$this->getDatasource(),
);
}
public function getDatasourceApplicationClass() {
return $this->getDatasource()->getDatasourceApplicationClass();
}
public function getBrowseTitle() {
return $this->getDatasource()->getBrowseTitle();
}
public function getPlaceholderText() {
return $this->getDatasource()->getPlaceholderText();
}
}

View file

@ -0,0 +1,70 @@
<?php
final class PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource
extends PhabricatorTypeaheadDatasource {
public function getBrowseTitle() {
return pht('Browse Any');
}
public function getPlaceholderText() {
return pht('Type "any()"...');
}
public function getDatasourceApplicationClass() {
return null;
}
public function getDatasourceFunctions() {
return array(
'any' => array(
'name' => pht('Any Value'),
'summary' => pht('Find results with any value.'),
'description' => pht(
"This function includes results which have any value. Use a query ".
"like this to find results with any value:\n\n%s".
'> any()'),
),
);
}
public function loadResults() {
$results = array(
$this->newAnyFunction(),
);
return $this->filterResultsAgainstTokens($results);
}
protected function evaluateFunction($function, array $argv_list) {
$results = array();
foreach ($argv_list as $argv) {
$results[] = new PhabricatorQueryConstraint(
PhabricatorQueryConstraint::OPERATOR_ANY,
null);
}
return $results;
}
public function renderFunctionTokens($function, array $argv_list) {
$results = array();
foreach ($argv_list as $argv) {
$results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult(
$this->newAnyFunction());
}
return $results;
}
private function newAnyFunction() {
$name = pht('Any Value');
return $this->newFunctionResult()
->setName($name.' any')
->setDisplayName($name)
->setIcon('fa-circle-o')
->setPHID('any()')
->setUnique(true)
->addAttribute(pht('Select results with any value.'));
}
}

View file

@ -0,0 +1,17 @@
<?php
final class PhabricatorCustomFieldApplicationSearchDatasource
extends PhabricatorTypeaheadProxyDatasource {
public function getComponentDatasources() {
$datasources = parent::getComponentDatasources();
$datasources[] =
new PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource();
$datasources[] =
new PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource();
return $datasources;
}
}

View file

@ -0,0 +1,72 @@
<?php
final class PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource
extends PhabricatorTypeaheadDatasource {
public function getBrowseTitle() {
return pht('Browse No Value');
}
public function getPlaceholderText() {
return pht('Type "none()"...');
}
public function getDatasourceApplicationClass() {
return null;
}
public function getDatasourceFunctions() {
return array(
'none' => array(
'name' => pht('No Value'),
'summary' => pht('Find results with no value.'),
'description' => pht(
"This function includes results which have no value. Use a query ".
"like this to find results with no value:\n\n%s\n\n",
'If you combine this function with other constraints, results '.
'which have no value or the specified values will be returned.',
'> any()'),
),
);
}
public function loadResults() {
$results = array(
$this->newNoneFunction(),
);
return $this->filterResultsAgainstTokens($results);
}
protected function evaluateFunction($function, array $argv_list) {
$results = array();
foreach ($argv_list as $argv) {
$results[] = new PhabricatorQueryConstraint(
PhabricatorQueryConstraint::OPERATOR_NULL,
null);
}
return $results;
}
public function renderFunctionTokens($function, array $argv_list) {
$results = array();
foreach ($argv_list as $argv) {
$results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult(
$this->newNoneFunction());
}
return $results;
}
private function newNoneFunction() {
$name = pht('No Value');
return $this->newFunctionResult()
->setName($name.' none')
->setDisplayName($name)
->setIcon('fa-ban')
->setPHID('none()')
->setUnique(true)
->addAttribute(pht('Select results with no value.'));
}
}

View file

@ -33,12 +33,28 @@ abstract class PhabricatorStandardCustomFieldTokenizer
$control = id(new AphrontFormTokenizerControl())
->setLabel($this->getFieldName())
->setName($this->getFieldKey())
->setDatasource($this->getDatasource())
->setDatasource($this->newApplicationSearchDatasource())
->setValue(nonempty($value, array()));
$form->appendControl($control);
}
public function applyApplicationSearchConstraintToQuery(
PhabricatorApplicationSearchEngine $engine,
PhabricatorCursorPagedPolicyAwareQuery $query,
$value) {
if ($value) {
$datasource = $this->newApplicationSearchDatasource()
->setViewer($this->getViewer());
$value = $datasource->evaluateTokens($value);
$query->withApplicationSearchContainsConstraint(
$this->newStringIndex(null),
$value);
}
}
public function getHeraldFieldValueType($condition) {
return id(new HeraldTokenizerFieldValue())
->setKey('custom.'.$this->getFieldKey())
@ -120,4 +136,11 @@ abstract class PhabricatorStandardCustomFieldTokenizer
}
}
protected function newApplicationSearchDatasource() {
$datasource = $this->getDatasource();
return id(new PhabricatorCustomFieldApplicationSearchDatasource())
->setDatasource($datasource);
}
}