@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

Add a date range query to Macros

Summary:
Ref T1163. Ref T2625. This could probably use some tweaks, but I kept things mostly-generic.

I added a new control for freeform dates so we can have it render help or whatever later on.

Test Plan: See screenshots.

Reviewers: chad, btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2625, T1163

Differential Revision: https://secure.phabricator.com/D6084

+182
+2
resources/sql/patches/20130530.macrodatekey.sql
··· 1 + ALTER TABLE {$NAMESPACE}_file.file_imagemacro 2 + ADD KEY `key_dateCreated` (dateCreated);
+2
src/__phutil_library_map__.php
··· 677 677 'PHUIFeedStoryExample' => 'applications/uiexample/examples/PHUIFeedStoryExample.php', 678 678 'PHUIFeedStoryView' => 'view/phui/PHUIFeedStoryView.php', 679 679 'PHUIFormDividerControl' => 'view/form/control/PHUIFormDividerControl.php', 680 + 'PHUIFormFreeformDateControl' => 'view/form/control/PHUIFormFreeformDateControl.php', 680 681 'PHUIFormMultiSubmitControl' => 'view/form/control/PHUIFormMultiSubmitControl.php', 681 682 'PHUIFormPageView' => 'view/form/PHUIFormPageView.php', 682 683 'PHUIIconExample' => 'applications/uiexample/examples/PHUIIconExample.php', ··· 2470 2471 'PHUIFeedStoryExample' => 'PhabricatorUIExample', 2471 2472 'PHUIFeedStoryView' => 'AphrontView', 2472 2473 'PHUIFormDividerControl' => 'AphrontFormControl', 2474 + 'PHUIFormFreeformDateControl' => 'AphrontFormControl', 2473 2475 'PHUIFormMultiSubmitControl' => 'AphrontFormControl', 2474 2476 'PHUIFormPageView' => 'AphrontView', 2475 2477 'PHUIIconExample' => 'PhabricatorUIExample',
+26
src/applications/macro/query/PhabricatorMacroQuery.php
··· 11 11 private $authors; 12 12 private $names; 13 13 private $nameLike; 14 + private $dateCreatedAfter; 15 + private $dateCreatedBefore; 14 16 15 17 private $status = 'status-any'; 16 18 const STATUS_ANY = 'status-any'; ··· 52 54 53 55 public function withStatus($status) { 54 56 $this->status = $status; 57 + return $this; 58 + } 59 + 60 + public function withDateCreatedBefore($date_created_before) { 61 + $this->dateCreatedBefore = $date_created_before; 62 + return $this; 63 + } 64 + 65 + public function withDateCreatedAfter($date_created_after) { 66 + $this->dateCreatedAfter = $date_created_after; 55 67 return $this; 56 68 } 57 69 ··· 123 135 break; 124 136 default: 125 137 throw new Exception("Unknown status '{$this->status}'!"); 138 + } 139 + 140 + if ($this->dateCreatedAfter) { 141 + $where[] = qsprintf( 142 + $conn, 143 + 'm.dateCreated >= %d', 144 + $this->dateCreatedAfter); 145 + } 146 + 147 + if ($this->dateCreatedBefore) { 148 + $where[] = qsprintf( 149 + $conn, 150 + 'm.dateCreated <= %d', 151 + $this->dateCreatedBefore); 126 152 } 127 153 128 154 $where[] = $this->buildPagingClause($conn);
+21
src/applications/macro/query/PhabricatorMacroSearchEngine.php
··· 12 12 $saved->setParameter('status', $request->getStr('status')); 13 13 $saved->setParameter('names', $request->getStrList('names')); 14 14 $saved->setParameter('nameLike', $request->getStr('nameLike')); 15 + $saved->setParameter('createdStart', $request->getStr('createdStart')); 16 + $saved->setParameter('createdEnd', $request->getStr('createdEnd')); 15 17 16 18 return $saved; 17 19 } ··· 39 41 $query->withNameLike($like); 40 42 } 41 43 44 + $start = $this->parseDateTime($saved->getParameter('createdStart')); 45 + $end = $this->parseDateTime($saved->getParameter('createdEnd')); 46 + 47 + if ($start) { 48 + $query->withDateCreatedAfter($start); 49 + } 50 + 51 + if ($end) { 52 + $query->withDateCreatedBefore($end); 53 + } 54 + 42 55 return $query; 43 56 } 44 57 ··· 79 92 ->setName('names') 80 93 ->setLabel(pht('Exact Names')) 81 94 ->setValue($names)); 95 + 96 + $this->buildDateRange( 97 + $form, 98 + $saved_query, 99 + 'createdStart', 100 + pht('Created After'), 101 + 'createdEnd', 102 + pht('Created Before')); 82 103 } 83 104 84 105 protected function getURI($path) {
+12
src/applications/search/controller/PhabricatorApplicationSearchController.php
··· 131 131 132 132 $engine->buildSearchForm($form, $saved_query); 133 133 134 + $errors = $engine->getErrors(); 135 + if ($errors) { 136 + $run_query = false; 137 + $errors = id(new AphrontErrorView()) 138 + ->setTitle(pht('Query Errors')) 139 + ->setErrors($errors); 140 + } 141 + 134 142 $submit = id(new AphrontFormSubmitControl()) 135 143 ->setValue(pht('Execute Query')); 136 144 ··· 182 190 } else { 183 191 $nav->appendChild($pager); 184 192 } 193 + } 194 + 195 + if ($errors) { 196 + $nav->appendChild($errors); 185 197 } 186 198 187 199 if ($named_query) {
+94
src/applications/search/engine/PhabricatorApplicationSearchEngine.php
··· 6 6 * 7 7 * @task builtin Builtin Queries 8 8 * @task uri Query URIs 9 + * @task dates Date Filters 9 10 * 10 11 * @group search 11 12 */ 12 13 abstract class PhabricatorApplicationSearchEngine { 13 14 14 15 private $viewer; 16 + private $errors = array(); 15 17 16 18 public function setViewer(PhabricatorUser $viewer) { 17 19 $this->viewer = $viewer; ··· 54 56 AphrontFormView $form, 55 57 PhabricatorSavedQuery $query); 56 58 59 + public function getErrors() { 60 + return $this->errors; 61 + } 62 + 63 + public function addError($error) { 64 + $this->errors[] = $error; 65 + return $this; 66 + } 57 67 58 68 /** 59 69 * Return an application URI corresponding to the results page of a query. ··· 181 191 */ 182 192 public function buildSavedQueryFromBuiltin($query_key) { 183 193 throw new Exception("Builtin '{$query_key}' is not supported!"); 194 + } 195 + 196 + 197 + /* -( Dates )-------------------------------------------------------------- */ 198 + 199 + 200 + /** 201 + * @task dates 202 + */ 203 + protected function parseDateTime($date_time) { 204 + if (!strlen($date_time)) { 205 + return null; 206 + } 207 + 208 + $viewer = $this->requireViewer(); 209 + 210 + $timezone = new DateTimeZone($viewer->getTimezoneIdentifier()); 211 + 212 + try { 213 + $date = new DateTime($date_time, $timezone); 214 + $timestamp = $date->format('U'); 215 + } catch (Exception $e) { 216 + $timestamp = null; 217 + } 218 + 219 + return $timestamp; 220 + } 221 + 222 + 223 + /** 224 + * @task dates 225 + */ 226 + protected function buildDateRange( 227 + AphrontFormView $form, 228 + PhabricatorSavedQuery $saved_query, 229 + $start_key, 230 + $start_name, 231 + $end_key, 232 + $end_name) { 233 + 234 + $start_str = $saved_query->getParameter($start_key); 235 + $start = null; 236 + if (strlen($start_str)) { 237 + $start = $this->parseDateTime($start_str); 238 + if (!$start) { 239 + $this->addError( 240 + pht( 241 + '"%s" date can not be parsed.', 242 + $start_name)); 243 + } 244 + } 245 + 246 + 247 + $end_str = $saved_query->getParameter($end_key); 248 + $end = null; 249 + if (strlen($end_str)) { 250 + $end = $this->parseDateTime($end_str); 251 + if (!$end) { 252 + $this->addError( 253 + pht( 254 + '"%s" date can not be parsed.', 255 + $end_name)); 256 + } 257 + } 258 + 259 + if ($start && $end && ($start >= $end)) { 260 + $this->addError( 261 + pht( 262 + '"%s" must be a date before "%s".', 263 + $start_name, 264 + $end_name)); 265 + } 266 + 267 + $form 268 + ->appendChild( 269 + id(new PHUIFormFreeformDateControl()) 270 + ->setName($start_key) 271 + ->setLabel($start_name) 272 + ->setValue($start_str)) 273 + ->appendChild( 274 + id(new AphrontFormTextControl()) 275 + ->setName($end_key) 276 + ->setLabel($end_name) 277 + ->setValue($end_str)); 184 278 } 185 279 }
+4
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1330 1330 'type' => 'php', 1331 1331 'name' => $this->getPatchPath('20130530.sessionhash.php'), 1332 1332 ), 1333 + '20130530.macrodatekey.sql' => array( 1334 + 'type' => 'sql', 1335 + 'name' => $this->getPatchPath('20130530.macrodatekey.sql'), 1336 + ), 1333 1337 ); 1334 1338 } 1335 1339 }
+21
src/view/form/control/PHUIFormFreeformDateControl.php
··· 1 + <?php 2 + 3 + final class PHUIFormFreeformDateControl extends AphrontFormControl { 4 + 5 + protected function getCustomControlClass() { 6 + return 'aphront-form-control-text'; 7 + } 8 + 9 + protected function renderInput() { 10 + return javelin_tag( 11 + 'input', 12 + array( 13 + 'type' => 'text', 14 + 'name' => $this->getName(), 15 + 'value' => $this->getValue(), 16 + 'disabled' => $this->getDisabled() ? 'disabled' : null, 17 + 'id' => $this->getID(), 18 + )); 19 + } 20 + 21 + }