@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+190 -27
+67 -3
src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php
··· 81 81 return $control; 82 82 } 83 83 84 - // TODO: Support ApplicationSearch for these fields. We build indexes above, 85 - // but don't provide a UI for searching. To do so, we need a reasonable date 86 - // range control and the ability to add a range constraint. 84 + public function readApplicationSearchValueFromRequest( 85 + PhabricatorApplicationSearchEngine $engine, 86 + AphrontRequest $request) { 87 + 88 + $key = $this->getFieldKey(); 89 + 90 + return array( 91 + 'min' => $request->getStr($key.'.min'), 92 + 'max' => $request->getStr($key.'.max'), 93 + ); 94 + } 95 + 96 + public function applyApplicationSearchConstraintToQuery( 97 + PhabricatorApplicationSearchEngine $engine, 98 + PhabricatorCursorPagedPolicyAwareQuery $query, 99 + $value) { 100 + 101 + $viewer = $this->getViewer(); 102 + 103 + if (!is_array($value)) { 104 + $value = array(); 105 + } 106 + 107 + $min_str = idx($value, 'min', ''); 108 + if (strlen($min_str)) { 109 + $min = PhabricatorTime::parseLocalTime($min_str, $viewer); 110 + } else { 111 + $min = null; 112 + } 113 + 114 + $max_str = idx($value, 'max', ''); 115 + if (strlen($max_str)) { 116 + $max = PhabricatorTime::parseLocalTime($max_str, $viewer); 117 + } else { 118 + $max = null; 119 + } 120 + 121 + if (($min !== null) || ($max !== null)) { 122 + $query->withApplicationSearchRangeConstraint( 123 + $this->newNumericIndex(null), 124 + $min, 125 + $max); 126 + } 127 + } 128 + 129 + public function appendToApplicationSearchForm( 130 + PhabricatorApplicationSearchEngine $engine, 131 + AphrontFormView $form, 132 + $value, 133 + array $handles) { 134 + 135 + if (!is_array($value)) { 136 + $value = array(); 137 + } 138 + 139 + $form 140 + ->appendChild( 141 + id(new AphrontFormTextControl()) 142 + ->setLabel(pht('%s After', $this->getFieldName())) 143 + ->setName($this->getFieldKey().'.min') 144 + ->setValue(idx($value, 'min', ''))) 145 + ->appendChild( 146 + id(new AphrontFormTextControl()) 147 + ->setLabel(pht('%s Before', $this->getFieldName())) 148 + ->setName($this->getFieldKey().'.max') 149 + ->setValue(idx($value, 'max', ''))); 150 + } 87 151 88 152 public function getApplicationTransactionTitle( 89 153 PhabricatorApplicationTransaction $xaction) {
+123 -24
src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
··· 285 285 286 286 287 287 /** 288 - * Constrain the query with an ApplicationSearch index. This adds a constraint 289 - * which requires objects to have one or more corresponding rows in the index 290 - * with one of the given values. Combined with appropriate indexes, it can 291 - * build the most common types of queries, like: 288 + * Constrain the query with an ApplicationSearch index, requiring field values 289 + * contain at least one of the values in a set. 290 + * 291 + * This constraint can build the most common types of queries, like: 292 292 * 293 293 * - Find users with shirt sizes "X" or "XL". 294 294 * - Find shoes with size "13". 295 295 * 296 296 * @param PhabricatorCustomFieldIndexStorage Table where the index is stored. 297 297 * @param string|list<string> One or more values to filter by. 298 + * @return this 298 299 * @task appsearch 299 300 */ 300 301 public function withApplicationSearchContainsConstraint( ··· 314 315 315 316 316 317 /** 318 + * Constrain the query with an ApplicationSearch index, requiring values 319 + * exist in a given range. 320 + * 321 + * This constraint is useful for expressing date ranges: 322 + * 323 + * - Find events between July 1st and July 7th. 324 + * 325 + * The ends of the range are inclusive, so a `$min` of `3` and a `$max` of 326 + * `5` will match fields with values `3`, `4`, or `5`. Providing `null` for 327 + * either end of the range will leave that end of the constraint open. 328 + * 329 + * @param PhabricatorCustomFieldIndexStorage Table where the index is stored. 330 + * @param int|null Minimum permissible value, inclusive. 331 + * @param int|null Maximum permissible value, inclusive. 332 + * @return this 333 + * @task appsearch 334 + */ 335 + public function withApplicationSearchRangeConstraint( 336 + PhabricatorCustomFieldIndexStorage $index, 337 + $min, 338 + $max) { 339 + 340 + $index_type = $index->getIndexValueType(); 341 + if ($index_type != 'int') { 342 + throw new Exception( 343 + pht( 344 + 'Attempting to apply a range constraint to a field with index type '. 345 + '"%s", expected type "%s".', 346 + $index_type, 347 + 'int')); 348 + } 349 + 350 + $this->applicationSearchConstraints[] = array( 351 + 'type' => $index->getIndexValueType(), 352 + 'cond' => 'range', 353 + 'table' => $index->getTableName(), 354 + 'index' => $index->getIndexKey(), 355 + 'value' => array($min, $max), 356 + ); 357 + 358 + return $this; 359 + } 360 + 361 + 362 + /** 317 363 * Get the name of the query's primary object PHID column, for constructing 318 364 * JOIN clauses. Normally (and by default) this is just `"phid"`, but if the 319 365 * query construction requires a table alias it may be something like ··· 339 385 foreach ($this->applicationSearchConstraints as $constraint) { 340 386 $type = $constraint['type']; 341 387 $value = $constraint['value']; 388 + $cond = $constraint['cond']; 342 389 343 - switch ($type) { 344 - case 'string': 345 - case 'int': 346 - if (count((array)$value) > 1) { 347 - return true; 390 + switch ($cond) { 391 + case '=': 392 + switch ($type) { 393 + case 'string': 394 + case 'int': 395 + if (count((array)$value) > 1) { 396 + return true; 397 + } 398 + break; 399 + default: 400 + throw new Exception(pht('Unknown index type "%s"!', $type)); 348 401 } 349 402 break; 403 + case 'range': 404 + // NOTE: It's possible to write a custom field where multiple rows 405 + // match a range constraint, but we don't currently ship any in the 406 + // upstream and I can't immediately come up with cases where this 407 + // would make sense. 408 + break; 350 409 default: 351 - throw new Exception("Unknown constraint type '{$type}!"); 410 + throw new Exception(pht('Unknown constraint condition "%s"!', $cond)); 352 411 } 353 412 } 354 413 ··· 395 454 $index = $constraint['index']; 396 455 $cond = $constraint['cond']; 397 456 $phid_column = $this->getApplicationSearchObjectPHIDColumn(); 398 - if ($cond !== '=') { 399 - throw new Exception("Unknown constraint condition '{$cond}'!"); 400 - } 457 + switch ($cond) { 458 + case '=': 459 + $type = $constraint['type']; 460 + switch ($type) { 461 + case 'string': 462 + $constraint_clause = qsprintf( 463 + $conn_r, 464 + '%T.indexValue IN (%Ls)', 465 + $alias, 466 + (array)$constraint['value']); 467 + break; 468 + case 'int': 469 + $constraint_clause = qsprintf( 470 + $conn_r, 471 + '%T.indexValue IN (%Ld)', 472 + $alias, 473 + (array)$constraint['value']); 474 + break; 475 + default: 476 + throw new Exception(pht('Unknown index type "%s"!', $type)); 477 + } 401 478 402 - $type = $constraint['type']; 403 - switch ($type) { 404 - case 'string': 405 479 $joins[] = qsprintf( 406 480 $conn_r, 407 481 'JOIN %T %T ON %T.objectPHID = %Q 408 482 AND %T.indexKey = %s 409 - AND %T.indexValue IN (%Ls)', 483 + AND (%Q)', 410 484 $table, 411 485 $alias, 412 486 $alias, 413 487 $phid_column, 414 488 $alias, 415 489 $index, 416 - $alias, 417 - (array)$constraint['value']); 490 + $constraint_clause); 418 491 break; 419 - case 'int': 492 + case 'range': 493 + list($min, $max) = $constraint['value']; 494 + if (($min === null) && ($max === null)) { 495 + // If there's no actual range constraint, just move on. 496 + break; 497 + } 498 + 499 + if ($min === null) { 500 + $constraint_clause = qsprintf( 501 + $conn_r, 502 + '%T.indexValue <= %d', 503 + $alias, 504 + $max); 505 + } else if ($max === null) { 506 + $constraint_clause = qsprintf( 507 + $conn_r, 508 + '%T.indexValue >= %d', 509 + $alias, 510 + $min); 511 + } else { 512 + $constraint_clause = qsprintf( 513 + $conn_r, 514 + '%T.indexValue BETWEEN %d AND %d', 515 + $alias, 516 + $min, 517 + $max); 518 + } 519 + 420 520 $joins[] = qsprintf( 421 521 $conn_r, 422 522 'JOIN %T %T ON %T.objectPHID = %Q 423 523 AND %T.indexKey = %s 424 - AND %T.indexValue IN (%Ld)', 524 + AND (%Q)', 425 525 $table, 426 526 $alias, 427 527 $alias, 428 528 $phid_column, 429 529 $alias, 430 530 $index, 431 - $alias, 432 - (array)$constraint['value']); 531 + $constraint_clause); 433 532 break; 434 533 default: 435 - throw new Exception("Unknown constraint type '{$type}'!"); 534 + throw new Exception(pht('Unknown constraint condition "%s"!', $cond)); 436 535 } 437 536 } 438 537