@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.

Drive Maniphest grouping and ordering through standard infrastructure

Summary: Ref T8441. Ref T7715. Ref T7909. Clean up all the ordering and grouping hacks in Maniphest so we can drive it through normal infrastructure, move it to SearchField, introduce Spaces, and eventually modernize the Conduit API.

Test Plan:
- Executed all grouping/ordering queries, including custom queries.
- Forced execution with old aliases; got modern results.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T7909, T7715, T8441

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

+155 -212
+4
resources/sql/autopatches/20150504.symbolsproject.1.php
··· 11 11 $raw_projects_data = ipull($raw_projects_data, null, 'id'); 12 12 13 13 $repository_ids = ipull($raw_projects_data, 'repositoryID'); 14 + if (!$repository_ids) { 15 + return; 16 + } 17 + 14 18 $repositories = id(new PhabricatorRepositoryQuery()) 15 19 ->setViewer(PhabricatorUser::getOmnipotentUser()) 16 20 ->withIDs($repository_ids)
+1 -1
src/applications/maniphest/__tests__/ManiphestTaskTestCase.php
··· 145 145 private function loadTasks(PhabricatorUser $viewer, $auto_base) { 146 146 $tasks = id(new ManiphestTaskQuery()) 147 147 ->setViewer($viewer) 148 - ->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY) 148 + ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) 149 149 ->execute(); 150 150 151 151 // NOTE: AUTO_INCREMENT changes survive ROLLBACK, and we can't throw them
+1 -1
src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php
··· 102 102 103 103 $order = $request->getValue('order'); 104 104 if ($order) { 105 - $query->setOrderBy($order); 105 + $query->setOrder($order); 106 106 } 107 107 108 108 $limit = $request->getValue('limit');
+1 -1
src/applications/maniphest/editor/ManiphestTransactionEditor.php
··· 632 632 633 633 $query = id(new ManiphestTaskQuery()) 634 634 ->setViewer(PhabricatorUser::getOmnipotentUser()) 635 - ->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY) 635 + ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) 636 636 ->withPriorities(array($dst->getPriority())) 637 637 ->setLimit(1); 638 638
+55 -68
src/applications/maniphest/query/ManiphestTaskQuery.php
··· 43 43 const GROUP_STATUS = 'group-status'; 44 44 const GROUP_PROJECT = 'group-project'; 45 45 46 - private $orderBy = 'order-modified'; 47 46 const ORDER_PRIORITY = 'order-priority'; 48 47 const ORDER_CREATED = 'order-created'; 49 48 const ORDER_MODIFIED = 'order-modified'; ··· 127 126 128 127 public function setGroupBy($group) { 129 128 $this->groupBy = $group; 130 - return $this; 131 - } 132 129 133 - public function setOrderBy($order) { 134 - $this->orderBy = $order; 130 + switch ($this->groupBy) { 131 + case self::GROUP_NONE: 132 + $vector = array(); 133 + break; 134 + case self::GROUP_PRIORITY: 135 + $vector = array('priority'); 136 + break; 137 + case self::GROUP_OWNER: 138 + $vector = array('owner'); 139 + break; 140 + case self::GROUP_STATUS: 141 + $vector = array('status'); 142 + break; 143 + case self::GROUP_PROJECT: 144 + $vector = array('project'); 145 + break; 146 + } 147 + 148 + $this->setGroupVector($vector); 149 + 135 150 return $this; 136 151 } 137 152 ··· 195 210 196 211 public function newResultObject() { 197 212 return new ManiphestTask(); 198 - } 199 - 200 - protected function willExecute() { 201 - // If we already have an order vector, use it as provided. 202 - // TODO: This is a messy hack to make setOrderVector() stronger than 203 - // setPriority(). 204 - $vector = $this->getOrderVector(); 205 - $keys = mpull(iterator_to_array($vector), 'getOrderKey'); 206 - if (array_values($keys) !== array('id')) { 207 - return; 208 - } 209 - 210 - $parts = array(); 211 - switch ($this->groupBy) { 212 - case self::GROUP_NONE: 213 - break; 214 - case self::GROUP_PRIORITY: 215 - $parts[] = array('priority'); 216 - break; 217 - case self::GROUP_OWNER: 218 - $parts[] = array('owner'); 219 - break; 220 - case self::GROUP_STATUS: 221 - $parts[] = array('status'); 222 - break; 223 - case self::GROUP_PROJECT: 224 - $parts[] = array('project'); 225 - break; 226 - } 227 - 228 - if ($this->applicationSearchOrders) { 229 - $columns = array(); 230 - foreach ($this->applicationSearchOrders as $order) { 231 - $part = 'custom:'.$order['key']; 232 - if ($order['ascending']) { 233 - $part = '-'.$part; 234 - } 235 - $columns[] = $part; 236 - } 237 - $columns[] = 'id'; 238 - $parts[] = $columns; 239 - } else { 240 - switch ($this->orderBy) { 241 - case self::ORDER_PRIORITY: 242 - $parts[] = array('priority', 'subpriority', 'id'); 243 - break; 244 - case self::ORDER_CREATED: 245 - $parts[] = array('id'); 246 - break; 247 - case self::ORDER_MODIFIED: 248 - $parts[] = array('updated', 'id'); 249 - break; 250 - case self::ORDER_TITLE: 251 - $parts[] = array('title', 'id'); 252 - break; 253 - } 254 - } 255 - 256 - $parts = array_mergev($parts); 257 - // We may have a duplicate column if we are both ordering and grouping 258 - // by priority. 259 - $parts = array_unique($parts); 260 - $this->setOrderVector($parts); 261 213 } 262 214 263 215 protected function loadPage() { ··· 758 710 } 759 711 760 712 return $id; 713 + } 714 + 715 + public function getBuiltinOrders() { 716 + $orders = array( 717 + 'priority' => array( 718 + 'vector' => array('priority', 'subpriority', 'id'), 719 + 'name' => pht('Priority'), 720 + 'aliases' => array(self::ORDER_PRIORITY), 721 + ), 722 + 'updated' => array( 723 + 'vector' => array('updated', 'id'), 724 + 'name' => pht('Date Updated'), 725 + 'aliases' => array(self::ORDER_MODIFIED), 726 + ), 727 + 'title' => array( 728 + 'vector' => array('title', 'id'), 729 + 'name' => pht('Title'), 730 + 'aliases' => array(self::ORDER_TITLE), 731 + ), 732 + ) + parent::getBuiltinOrders(); 733 + 734 + // Alias the "newest" builtin to the historical key for it. 735 + $orders['newest']['aliases'][] = self::ORDER_CREATED; 736 + 737 + $orders = array_select_keys( 738 + $orders, 739 + array( 740 + 'priority', 741 + 'updated', 742 + 'newest', 743 + 'oldest', 744 + 'title', 745 + )) + $orders; 746 + 747 + return $orders; 761 748 } 762 749 763 750 public function getOrderableColumns() {
+13 -29
src/applications/maniphest/query/ManiphestTaskSearchEngine.php
··· 38 38 return 'PhabricatorManiphestApplication'; 39 39 } 40 40 41 - public function getCustomFieldObject() { 42 - return new ManiphestTask(); 41 + public function newQuery() { 42 + return id(new ManiphestTaskQuery()) 43 + ->needProjectPHIDs(true); 43 44 } 44 45 45 46 public function buildSavedQueryFromRequest(AphrontRequest $request) { ··· 108 109 } 109 110 110 111 public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 111 - $query = id(new ManiphestTaskQuery()) 112 - ->needProjectPHIDs(true); 112 + $query = $this->newQuery(); 113 113 114 114 $viewer = $this->requireViewer(); 115 115 ··· 156 156 $query->withBlockingTasks($saved->getParameter('blocking')); 157 157 $query->withBlockedTasks($saved->getParameter('blocked')); 158 158 159 - $this->applyOrderByToQuery( 160 - $query, 161 - $this->getOrderValues(), 162 - $saved->getParameter('order')); 159 + // TODO: This is glue that will be obsolete soon. 160 + $order = $saved->getParameter('order'); 161 + $builtin = $query->getBuiltinOrderAliasMap(); 162 + if (strlen($order) && isset($builtin[$order])) { 163 + $query->setOrder($order); 164 + } else { 165 + $query->setOrder(head_key($builtin)); 166 + } 163 167 164 168 $group = $saved->getParameter('group'); 165 169 $group = idx($this->getGroupValues(), $group); ··· 246 250 247 251 $ids = $saved->getParameter('ids', array()); 248 252 249 - $builtin_orders = $this->getOrderOptions(); 250 - $custom_orders = $this->getCustomFieldOrderOptions(); 251 - $all_orders = $builtin_orders + $custom_orders; 253 + $all_orders = ipull($this->newQuery()->getBuiltinOrders(), 'name'); 252 254 253 255 $form 254 256 ->appendControl( ··· 403 405 } 404 406 405 407 return parent::buildSavedQueryFromBuiltin($query_key); 406 - } 407 - 408 - private function getOrderOptions() { 409 - return array( 410 - 'priority' => pht('Priority'), 411 - 'updated' => pht('Date Updated'), 412 - 'created' => pht('Date Created'), 413 - 'title' => pht('Title'), 414 - ); 415 - } 416 - 417 - private function getOrderValues() { 418 - return array( 419 - 'priority' => ManiphestTaskQuery::ORDER_PRIORITY, 420 - 'updated' => ManiphestTaskQuery::ORDER_MODIFIED, 421 - 'created' => ManiphestTaskQuery::ORDER_CREATED, 422 - 'title' => ManiphestTaskQuery::ORDER_TITLE, 423 - ); 424 408 } 425 409 426 410 private function getGroupOptions() {
+1 -1
src/applications/project/controller/PhabricatorProjectBoardViewController.php
··· 163 163 PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, 164 164 PhabricatorQueryConstraint::OPERATOR_AND, 165 165 array($project->getPHID())) 166 - ->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY) 166 + ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) 167 167 ->setViewer($viewer) 168 168 ->execute(); 169 169 $tasks = mpull($tasks, null, 'getPHID');
+1 -61
src/applications/search/engine/PhabricatorApplicationSearchEngine.php
··· 157 157 } 158 158 159 159 $order = $saved->getParameter('order'); 160 - $builtin = $query->getBuiltinOrders(); 160 + $builtin = $query->getBuiltinOrderAliasMap(); 161 161 if (strlen($order) && isset($builtin[$order])) { 162 162 $query->setOrder($order); 163 163 } else { ··· 1111 1111 $query, 1112 1112 $saved->getParameter($key)); 1113 1113 } 1114 - } 1115 - 1116 - protected function applyOrderByToQuery( 1117 - PhabricatorCursorPagedPolicyAwareQuery $query, 1118 - array $standard_values, 1119 - $order) { 1120 - 1121 - if (substr($order, 0, 7) === 'custom:') { 1122 - $list = $this->getCustomFieldList(); 1123 - if (!$list) { 1124 - $query->setOrderBy(head($standard_values)); 1125 - return; 1126 - } 1127 - 1128 - foreach ($list->getFields() as $field) { 1129 - $key = $this->getKeyForCustomField($field); 1130 - 1131 - if ($key === $order) { 1132 - $index = $field->buildOrderIndex(); 1133 - 1134 - if ($index === null) { 1135 - $query->setOrderBy(head($standard_values)); 1136 - return; 1137 - } 1138 - 1139 - $query->withApplicationSearchOrder( 1140 - $field, 1141 - $index, 1142 - false); 1143 - break; 1144 - } 1145 - } 1146 - } else { 1147 - $order = idx($standard_values, $order); 1148 - if ($order) { 1149 - $query->setOrderBy($order); 1150 - } else { 1151 - $query->setOrderBy(head($standard_values)); 1152 - } 1153 - } 1154 - } 1155 - 1156 - 1157 - protected function getCustomFieldOrderOptions() { 1158 - $list = $this->getCustomFieldList(); 1159 - if (!$list) { 1160 - return; 1161 - } 1162 - 1163 - $custom_order = array(); 1164 - foreach ($list->getFields() as $field) { 1165 - if ($field->shouldAppearInApplicationSearch()) { 1166 - if ($field->buildOrderIndex() !== null) { 1167 - $key = $this->getKeyForCustomField($field); 1168 - $custom_order[$key] = $field->getFieldName(); 1169 - } 1170 - } 1171 - } 1172 - 1173 - return $custom_order; 1174 1114 } 1175 1115 1176 1116 /**
+16
src/infrastructure/query/order/PhabricatorQueryOrderVector.php
··· 84 84 return $obj; 85 85 } 86 86 87 + public function appendVector($vector) { 88 + $vector = self::newFromVector($vector); 89 + 90 + // When combining vectors (like "group by" and "order by" vectors), there 91 + // may be redundant columns. We only want to append unique columns which 92 + // aren't already present in the vector. 93 + foreach ($vector->items as $key => $item) { 94 + if (empty($this->items[$key])) { 95 + $this->items[$key] = $item; 96 + $this->keys[] = $key; 97 + } 98 + } 99 + 100 + return $this; 101 + } 102 + 87 103 public function getAsString() { 88 104 $scalars = array(); 89 105 foreach ($this->items as $item) {
+62 -50
src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
··· 18 18 private $afterID; 19 19 private $beforeID; 20 20 private $applicationSearchConstraints = array(); 21 - protected $applicationSearchOrders = array(); 22 21 private $internalPaging; 23 22 private $orderVector; 23 + private $groupVector; 24 24 private $builtinOrder; 25 25 private $edgeLogicConstraints = array(); 26 26 private $edgeLogicConstraintsAreValid = false; ··· 628 628 * @task order 629 629 */ 630 630 public function setOrder($order) { 631 - $orders = $this->getBuiltinOrders(); 631 + $aliases = $this->getBuiltinOrderAliasMap(); 632 632 633 - if (empty($orders[$order])) { 633 + if (empty($aliases[$order])) { 634 634 throw new Exception( 635 635 pht( 636 636 'Query "%s" does not support a builtin order "%s". Supported orders '. 637 637 'are: %s.', 638 638 get_class($this), 639 639 $order, 640 - implode(', ', array_keys($orders)))); 640 + implode(', ', array_keys($aliases)))); 641 641 } 642 642 643 - $this->builtinOrder = $order; 643 + $this->builtinOrder = $aliases[$order]; 644 + $this->orderVector = null; 645 + 646 + return $this; 647 + } 648 + 649 + 650 + /** 651 + * Set a grouping order to apply before primary result ordering. 652 + * 653 + * This allows you to preface the query order vector with additional orders, 654 + * so you can effect "group by" queries while still respecting "order by". 655 + * 656 + * This is a high-level method which works alongside @{method:setOrder}. For 657 + * lower-level control over order vectors, use @{method:setOrderVector}. 658 + * 659 + * @param PhabricatorQueryOrderVector|list<string> List of order keys. 660 + * @return this 661 + * @task order 662 + */ 663 + public function setGroupVector($vector) { 664 + $this->groupVector = $vector; 644 665 $this->orderVector = null; 645 666 646 667 return $this; ··· 707 728 return $orders; 708 729 } 709 730 731 + public function getBuiltinOrderAliasMap() { 732 + $orders = $this->getBuiltinOrders(); 733 + 734 + $map = array(); 735 + foreach ($orders as $key => $order) { 736 + $keys = array(); 737 + $keys[] = $key; 738 + foreach (idx($order, 'aliases', array()) as $alias) { 739 + $keys[] = $alias; 740 + } 741 + 742 + foreach ($keys as $alias) { 743 + if (isset($map[$alias])) { 744 + throw new Exception( 745 + pht( 746 + 'Two builtin orders ("%s" and "%s") define the same key or '. 747 + 'alias ("%s"). Each order alias and key must be unique and '. 748 + 'identify a single order.', 749 + $key, 750 + $map[$alias], 751 + $alias)); 752 + } 753 + $map[$alias] = $key; 754 + } 755 + } 756 + 757 + return $map; 758 + } 759 + 710 760 711 761 /** 712 762 * Set a low-level column ordering. ··· 791 841 } else { 792 842 $vector = $this->getDefaultOrderVector(); 793 843 } 844 + 845 + if ($this->groupVector) { 846 + $group = PhabricatorQueryOrderVector::newFromVector($this->groupVector); 847 + $group->appendVector($vector); 848 + $vector = $group; 849 + } 850 + 794 851 $vector = PhabricatorQueryOrderVector::newFromVector($vector); 795 852 796 853 // We call setOrderVector() here to apply checks to the default vector. ··· 1023 1080 1024 1081 1025 1082 /** 1026 - * Order the results by an ApplicationSearch index. 1027 - * 1028 - * @param PhabricatorCustomField Field to which the index belongs. 1029 - * @param PhabricatorCustomFieldIndexStorage Table where the index is stored. 1030 - * @param bool True to sort ascending. 1031 - * @return this 1032 - * @task appsearch 1033 - */ 1034 - public function withApplicationSearchOrder( 1035 - PhabricatorCustomField $field, 1036 - PhabricatorCustomFieldIndexStorage $index, 1037 - $ascending) { 1038 - 1039 - $this->applicationSearchOrders[] = array( 1040 - 'key' => $field->getFieldKey(), 1041 - 'type' => $index->getIndexValueType(), 1042 - 'table' => $index->getTableName(), 1043 - 'index' => $index->getIndexKey(), 1044 - 'ascending' => $ascending, 1045 - ); 1046 - 1047 - return $this; 1048 - } 1049 - 1050 - 1051 - /** 1052 1083 * Get the name of the query's primary object PHID column, for constructing 1053 1084 * JOIN clauses. Normally (and by default) this is just `"phid"`, but it may 1054 1085 * be something more exotic. ··· 1230 1261 default: 1231 1262 throw new Exception(pht('Unknown constraint condition "%s"!', $cond)); 1232 1263 } 1233 - } 1234 - 1235 - // TODO: Get rid of this. 1236 - foreach ($this->applicationSearchOrders as $key => $order) { 1237 - $table = $order['table']; 1238 - $index = $order['index']; 1239 - $alias = 'appsearch_order_'.$index; 1240 - $phid_column = $this->getApplicationSearchObjectPHIDColumn(); 1241 - 1242 - $joins[] = qsprintf( 1243 - $conn_r, 1244 - 'LEFT JOIN %T %T ON %T.objectPHID = %Q 1245 - AND %T.indexKey = %s', 1246 - $table, 1247 - $alias, 1248 - $alias, 1249 - $phid_column, 1250 - $alias, 1251 - $index); 1252 1264 } 1253 1265 1254 1266 $phid_column = $this->getApplicationSearchObjectPHIDColumn();