mirror of
https://we.phorge.it/source/phorge.git
synced 2025-04-08 02:18:33 +02:00
Allow filtering of "date" custom fields
Summary: Ref T4663. Ref T4659. Allows "date" fields to be filtered with range parameters. Test Plan: - Added a custom "date" field with "search". - Populated some values. - Searched for dates using new range filters. - Combined date search with other searches. - Ran other searches independently. - Inspected the generated queries. Reviewers: btrahan Reviewed By: btrahan Subscribers: shadowhand, epriestley Maniphest Tasks: T4659, T4663 Differential Revision: https://secure.phabricator.com/D8598
This commit is contained in:
parent
17dee98d32
commit
8e88187835
2 changed files with 190 additions and 27 deletions
|
@ -81,9 +81,73 @@ final class PhabricatorStandardCustomFieldDate
|
||||||
return $control;
|
return $control;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Support ApplicationSearch for these fields. We build indexes above,
|
public function readApplicationSearchValueFromRequest(
|
||||||
// but don't provide a UI for searching. To do so, we need a reasonable date
|
PhabricatorApplicationSearchEngine $engine,
|
||||||
// range control and the ability to add a range constraint.
|
AphrontRequest $request) {
|
||||||
|
|
||||||
|
$key = $this->getFieldKey();
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'min' => $request->getStr($key.'.min'),
|
||||||
|
'max' => $request->getStr($key.'.max'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyApplicationSearchConstraintToQuery(
|
||||||
|
PhabricatorApplicationSearchEngine $engine,
|
||||||
|
PhabricatorCursorPagedPolicyAwareQuery $query,
|
||||||
|
$value) {
|
||||||
|
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
if (!is_array($value)) {
|
||||||
|
$value = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$min_str = idx($value, 'min', '');
|
||||||
|
if (strlen($min_str)) {
|
||||||
|
$min = PhabricatorTime::parseLocalTime($min_str, $viewer);
|
||||||
|
} else {
|
||||||
|
$min = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$max_str = idx($value, 'max', '');
|
||||||
|
if (strlen($max_str)) {
|
||||||
|
$max = PhabricatorTime::parseLocalTime($max_str, $viewer);
|
||||||
|
} else {
|
||||||
|
$max = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($min !== null) || ($max !== null)) {
|
||||||
|
$query->withApplicationSearchRangeConstraint(
|
||||||
|
$this->newNumericIndex(null),
|
||||||
|
$min,
|
||||||
|
$max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function appendToApplicationSearchForm(
|
||||||
|
PhabricatorApplicationSearchEngine $engine,
|
||||||
|
AphrontFormView $form,
|
||||||
|
$value,
|
||||||
|
array $handles) {
|
||||||
|
|
||||||
|
if (!is_array($value)) {
|
||||||
|
$value = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$form
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTextControl())
|
||||||
|
->setLabel(pht('%s After', $this->getFieldName()))
|
||||||
|
->setName($this->getFieldKey().'.min')
|
||||||
|
->setValue(idx($value, 'min', '')))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTextControl())
|
||||||
|
->setLabel(pht('%s Before', $this->getFieldName()))
|
||||||
|
->setName($this->getFieldKey().'.max')
|
||||||
|
->setValue(idx($value, 'max', '')));
|
||||||
|
}
|
||||||
|
|
||||||
public function getApplicationTransactionTitle(
|
public function getApplicationTransactionTitle(
|
||||||
PhabricatorApplicationTransaction $xaction) {
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
|
|
@ -285,16 +285,17 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constrain the query with an ApplicationSearch index. This adds a constraint
|
* Constrain the query with an ApplicationSearch index, requiring field values
|
||||||
* which requires objects to have one or more corresponding rows in the index
|
* contain at least one of the values in a set.
|
||||||
* with one of the given values. Combined with appropriate indexes, it can
|
*
|
||||||
* build the most common types of queries, like:
|
* This constraint can build the most common types of queries, like:
|
||||||
*
|
*
|
||||||
* - Find users with shirt sizes "X" or "XL".
|
* - Find users with shirt sizes "X" or "XL".
|
||||||
* - Find shoes with size "13".
|
* - Find shoes with size "13".
|
||||||
*
|
*
|
||||||
* @param PhabricatorCustomFieldIndexStorage Table where the index is stored.
|
* @param PhabricatorCustomFieldIndexStorage Table where the index is stored.
|
||||||
* @param string|list<string> One or more values to filter by.
|
* @param string|list<string> One or more values to filter by.
|
||||||
|
* @return this
|
||||||
* @task appsearch
|
* @task appsearch
|
||||||
*/
|
*/
|
||||||
public function withApplicationSearchContainsConstraint(
|
public function withApplicationSearchContainsConstraint(
|
||||||
|
@ -313,6 +314,51 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constrain the query with an ApplicationSearch index, requiring values
|
||||||
|
* exist in a given range.
|
||||||
|
*
|
||||||
|
* This constraint is useful for expressing date ranges:
|
||||||
|
*
|
||||||
|
* - Find events between July 1st and July 7th.
|
||||||
|
*
|
||||||
|
* The ends of the range are inclusive, so a `$min` of `3` and a `$max` of
|
||||||
|
* `5` will match fields with values `3`, `4`, or `5`. Providing `null` for
|
||||||
|
* either end of the range will leave that end of the constraint open.
|
||||||
|
*
|
||||||
|
* @param PhabricatorCustomFieldIndexStorage Table where the index is stored.
|
||||||
|
* @param int|null Minimum permissible value, inclusive.
|
||||||
|
* @param int|null Maximum permissible value, inclusive.
|
||||||
|
* @return this
|
||||||
|
* @task appsearch
|
||||||
|
*/
|
||||||
|
public function withApplicationSearchRangeConstraint(
|
||||||
|
PhabricatorCustomFieldIndexStorage $index,
|
||||||
|
$min,
|
||||||
|
$max) {
|
||||||
|
|
||||||
|
$index_type = $index->getIndexValueType();
|
||||||
|
if ($index_type != 'int') {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Attempting to apply a range constraint to a field with index type '.
|
||||||
|
'"%s", expected type "%s".',
|
||||||
|
$index_type,
|
||||||
|
'int'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->applicationSearchConstraints[] = array(
|
||||||
|
'type' => $index->getIndexValueType(),
|
||||||
|
'cond' => 'range',
|
||||||
|
'table' => $index->getTableName(),
|
||||||
|
'index' => $index->getIndexKey(),
|
||||||
|
'value' => array($min, $max),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of the query's primary object PHID column, for constructing
|
* Get the name of the query's primary object PHID column, for constructing
|
||||||
* JOIN clauses. Normally (and by default) this is just `"phid"`, but if the
|
* JOIN clauses. Normally (and by default) this is just `"phid"`, but if the
|
||||||
|
@ -339,7 +385,10 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
||||||
foreach ($this->applicationSearchConstraints as $constraint) {
|
foreach ($this->applicationSearchConstraints as $constraint) {
|
||||||
$type = $constraint['type'];
|
$type = $constraint['type'];
|
||||||
$value = $constraint['value'];
|
$value = $constraint['value'];
|
||||||
|
$cond = $constraint['cond'];
|
||||||
|
|
||||||
|
switch ($cond) {
|
||||||
|
case '=':
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'string':
|
case 'string':
|
||||||
case 'int':
|
case 'int':
|
||||||
|
@ -348,7 +397,17 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Exception("Unknown constraint type '{$type}!");
|
throw new Exception(pht('Unknown index type "%s"!', $type));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'range':
|
||||||
|
// NOTE: It's possible to write a custom field where multiple rows
|
||||||
|
// match a range constraint, but we don't currently ship any in the
|
||||||
|
// upstream and I can't immediately come up with cases where this
|
||||||
|
// would make sense.
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception(pht('Unknown constraint condition "%s"!', $cond));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,44 +454,84 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
||||||
$index = $constraint['index'];
|
$index = $constraint['index'];
|
||||||
$cond = $constraint['cond'];
|
$cond = $constraint['cond'];
|
||||||
$phid_column = $this->getApplicationSearchObjectPHIDColumn();
|
$phid_column = $this->getApplicationSearchObjectPHIDColumn();
|
||||||
if ($cond !== '=') {
|
switch ($cond) {
|
||||||
throw new Exception("Unknown constraint condition '{$cond}'!");
|
case '=':
|
||||||
}
|
|
||||||
|
|
||||||
$type = $constraint['type'];
|
$type = $constraint['type'];
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'string':
|
case 'string':
|
||||||
$joins[] = qsprintf(
|
$constraint_clause = qsprintf(
|
||||||
$conn_r,
|
$conn_r,
|
||||||
'JOIN %T %T ON %T.objectPHID = %Q
|
'%T.indexValue IN (%Ls)',
|
||||||
AND %T.indexKey = %s
|
|
||||||
AND %T.indexValue IN (%Ls)',
|
|
||||||
$table,
|
|
||||||
$alias,
|
|
||||||
$alias,
|
|
||||||
$phid_column,
|
|
||||||
$alias,
|
|
||||||
$index,
|
|
||||||
$alias,
|
$alias,
|
||||||
(array)$constraint['value']);
|
(array)$constraint['value']);
|
||||||
break;
|
break;
|
||||||
case 'int':
|
case 'int':
|
||||||
|
$constraint_clause = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'%T.indexValue IN (%Ld)',
|
||||||
|
$alias,
|
||||||
|
(array)$constraint['value']);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception(pht('Unknown index type "%s"!', $type));
|
||||||
|
}
|
||||||
|
|
||||||
$joins[] = qsprintf(
|
$joins[] = qsprintf(
|
||||||
$conn_r,
|
$conn_r,
|
||||||
'JOIN %T %T ON %T.objectPHID = %Q
|
'JOIN %T %T ON %T.objectPHID = %Q
|
||||||
AND %T.indexKey = %s
|
AND %T.indexKey = %s
|
||||||
AND %T.indexValue IN (%Ld)',
|
AND (%Q)',
|
||||||
$table,
|
$table,
|
||||||
$alias,
|
$alias,
|
||||||
$alias,
|
$alias,
|
||||||
$phid_column,
|
$phid_column,
|
||||||
$alias,
|
$alias,
|
||||||
$index,
|
$index,
|
||||||
|
$constraint_clause);
|
||||||
|
break;
|
||||||
|
case 'range':
|
||||||
|
list($min, $max) = $constraint['value'];
|
||||||
|
if (($min === null) && ($max === null)) {
|
||||||
|
// If there's no actual range constraint, just move on.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($min === null) {
|
||||||
|
$constraint_clause = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'%T.indexValue <= %d',
|
||||||
$alias,
|
$alias,
|
||||||
(array)$constraint['value']);
|
$max);
|
||||||
|
} else if ($max === null) {
|
||||||
|
$constraint_clause = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'%T.indexValue >= %d',
|
||||||
|
$alias,
|
||||||
|
$min);
|
||||||
|
} else {
|
||||||
|
$constraint_clause = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'%T.indexValue BETWEEN %d AND %d',
|
||||||
|
$alias,
|
||||||
|
$min,
|
||||||
|
$max);
|
||||||
|
}
|
||||||
|
|
||||||
|
$joins[] = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'JOIN %T %T ON %T.objectPHID = %Q
|
||||||
|
AND %T.indexKey = %s
|
||||||
|
AND (%Q)',
|
||||||
|
$table,
|
||||||
|
$alias,
|
||||||
|
$alias,
|
||||||
|
$phid_column,
|
||||||
|
$alias,
|
||||||
|
$index,
|
||||||
|
$constraint_clause);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Exception("Unknown constraint type '{$type}'!");
|
throw new Exception(pht('Unknown constraint condition "%s"!', $cond));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue