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

Implement a rough initial version of ApplicationSearch-driven Conduit read endpoints

Summary:
Ref T9964. See that task for some context and discussion.

Ref T7715, which has the bigger picture here.

Basically, I want Conduit read endpoints to be full-power, ApplicationSearch-driven endpoints, so that applications can:

- Write one EditEngine and get web + conduit writes for free.
- Write one SearchEngine and get web + conduit reads for free.

I previously made some steps toward this, but this puts more of the structure in place.

Test Plan:
Viewed API console endpoint and read 20 pages of docs:

{F1021961}

Made various calls: with query keys, constraints, pagination, and limits.

Viewed new {nav Config > Modules} page.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T7715, T9964

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

+950 -1
+19
src/__phutil_library_map__.php
··· 236 236 'ConduitMethodNotFoundException' => 'applications/conduit/protocol/exception/ConduitMethodNotFoundException.php', 237 237 'ConduitPingConduitAPIMethod' => 'applications/conduit/method/ConduitPingConduitAPIMethod.php', 238 238 'ConduitQueryConduitAPIMethod' => 'applications/conduit/method/ConduitQueryConduitAPIMethod.php', 239 + 'ConduitResultSearchEngineExtension' => 'applications/conduit/query/ConduitResultSearchEngineExtension.php', 239 240 'ConduitSSHWorkflow' => 'applications/conduit/ssh/ConduitSSHWorkflow.php', 240 241 'ConduitTokenGarbageCollector' => 'applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php', 241 242 'ConpherenceColumnViewController' => 'applications/conpherence/controller/ConpherenceColumnViewController.php', ··· 1557 1558 'PasteMailReceiver' => 'applications/paste/mail/PasteMailReceiver.php', 1558 1559 'PasteQueryConduitAPIMethod' => 'applications/paste/conduit/PasteQueryConduitAPIMethod.php', 1559 1560 'PasteReplyHandler' => 'applications/paste/mail/PasteReplyHandler.php', 1561 + 'PasteSearchConduitAPIMethod' => 'applications/paste/conduit/PasteSearchConduitAPIMethod.php', 1560 1562 'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php', 1561 1563 'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php', 1562 1564 'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php', ··· 1880 1882 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php', 1881 1883 'PhabricatorConduitMethodQuery' => 'applications/conduit/query/PhabricatorConduitMethodQuery.php', 1882 1884 'PhabricatorConduitRequestExceptionHandler' => 'aphront/handler/PhabricatorConduitRequestExceptionHandler.php', 1885 + 'PhabricatorConduitResultInterface' => 'applications/conduit/interface/PhabricatorConduitResultInterface.php', 1883 1886 'PhabricatorConduitSearchEngine' => 'applications/conduit/query/PhabricatorConduitSearchEngine.php', 1884 1887 'PhabricatorConduitTestCase' => '__tests__/PhabricatorConduitTestCase.php', 1885 1888 'PhabricatorConduitToken' => 'applications/conduit/storage/PhabricatorConduitToken.php', ··· 2364 2367 'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php', 2365 2368 'PhabricatorLipsumMondrianArtist' => 'applications/lipsum/image/PhabricatorLipsumMondrianArtist.php', 2366 2369 'PhabricatorLiskDAO' => 'infrastructure/storage/lisk/PhabricatorLiskDAO.php', 2370 + 'PhabricatorLiskSearchEngineExtension' => 'applications/search/engineextension/PhabricatorLiskSearchEngineExtension.php', 2367 2371 'PhabricatorLiskSerializer' => 'infrastructure/storage/lisk/PhabricatorLiskSerializer.php', 2368 2372 'PhabricatorListFilterUIExample' => 'applications/uiexample/examples/PhabricatorListFilterUIExample.php', 2369 2373 'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php', ··· 2745 2749 'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php', 2746 2750 'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php', 2747 2751 'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php', 2752 + 'PhabricatorPolicySearchEngineExtension' => 'applications/policy/engineextension/PhabricatorPolicySearchEngineExtension.php', 2748 2753 'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php', 2749 2754 'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php', 2750 2755 'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php', ··· 2980 2985 'PhabricatorSearchDocumentTypeDatasource' => 'applications/search/typeahead/PhabricatorSearchDocumentTypeDatasource.php', 2981 2986 'PhabricatorSearchEditController' => 'applications/search/controller/PhabricatorSearchEditController.php', 2982 2987 'PhabricatorSearchEngine' => 'applications/search/engine/PhabricatorSearchEngine.php', 2988 + 'PhabricatorSearchEngineAPIMethod' => 'applications/search/engine/PhabricatorSearchEngineAPIMethod.php', 2989 + 'PhabricatorSearchEngineExtension' => 'applications/search/engineextension/PhabricatorSearchEngineExtension.php', 2990 + 'PhabricatorSearchEngineExtensionModule' => 'applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php', 2983 2991 'PhabricatorSearchEngineTestCase' => 'applications/search/engine/__tests__/PhabricatorSearchEngineTestCase.php', 2984 2992 'PhabricatorSearchField' => 'applications/search/field/PhabricatorSearchField.php', 2985 2993 'PhabricatorSearchHovercardController' => 'applications/search/controller/PhabricatorSearchHovercardController.php', ··· 3067 3075 'PhabricatorSpacesNoAccessController' => 'applications/spaces/controller/PhabricatorSpacesNoAccessController.php', 3068 3076 'PhabricatorSpacesRemarkupRule' => 'applications/spaces/remarkup/PhabricatorSpacesRemarkupRule.php', 3069 3077 'PhabricatorSpacesSchemaSpec' => 'applications/spaces/storage/PhabricatorSpacesSchemaSpec.php', 3078 + 'PhabricatorSpacesSearchEngineExtension' => 'applications/spaces/engineextension/PhabricatorSpacesSearchEngineExtension.php', 3070 3079 'PhabricatorSpacesSearchField' => 'applications/spaces/searchfield/PhabricatorSpacesSearchField.php', 3071 3080 'PhabricatorSpacesTestCase' => 'applications/spaces/__tests__/PhabricatorSpacesTestCase.php', 3072 3081 'PhabricatorSpacesViewController' => 'applications/spaces/controller/PhabricatorSpacesViewController.php', ··· 4063 4072 'ConduitMethodNotFoundException' => 'ConduitException', 4064 4073 'ConduitPingConduitAPIMethod' => 'ConduitAPIMethod', 4065 4074 'ConduitQueryConduitAPIMethod' => 'ConduitAPIMethod', 4075 + 'ConduitResultSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 4066 4076 'ConduitSSHWorkflow' => 'PhabricatorSSHWorkflow', 4067 4077 'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector', 4068 4078 'ConpherenceColumnViewController' => 'ConpherenceController', ··· 5577 5587 'PasteMailReceiver' => 'PhabricatorObjectMailReceiver', 5578 5588 'PasteQueryConduitAPIMethod' => 'PasteConduitAPIMethod', 5579 5589 'PasteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 5590 + 'PasteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 5580 5591 'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability', 5581 5592 'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability', 5582 5593 'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector', ··· 5962 5973 ), 5963 5974 'PhabricatorConduitMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5964 5975 'PhabricatorConduitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 5976 + 'PhabricatorConduitResultInterface' => 'PhabricatorPHIDInterface', 5965 5977 'PhabricatorConduitSearchEngine' => 'PhabricatorApplicationSearchEngine', 5966 5978 'PhabricatorConduitTestCase' => 'PhabricatorTestCase', 5967 5979 'PhabricatorConduitToken' => array( ··· 6527 6539 'PhabricatorLipsumManagementWorkflow' => 'PhabricatorManagementWorkflow', 6528 6540 'PhabricatorLipsumMondrianArtist' => 'PhabricatorLipsumArtist', 6529 6541 'PhabricatorLiskDAO' => 'LiskDAO', 6542 + 'PhabricatorLiskSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 6530 6543 'PhabricatorLiskSerializer' => 'Phobject', 6531 6544 'PhabricatorListFilterUIExample' => 'PhabricatorUIExample', 6532 6545 'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine', ··· 6823 6836 'PhabricatorDestructibleInterface', 6824 6837 'PhabricatorApplicationTransactionInterface', 6825 6838 'PhabricatorSpacesInterface', 6839 + 'PhabricatorConduitResultInterface', 6826 6840 ), 6827 6841 'PhabricatorPasteApplication' => 'PhabricatorApplication', 6828 6842 'PhabricatorPasteArchiveController' => 'PhabricatorPasteController', ··· 6965 6979 'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 6966 6980 'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 6967 6981 'PhabricatorPolicyRule' => 'Phobject', 6982 + 'PhabricatorPolicySearchEngineExtension' => 'PhabricatorSearchEngineExtension', 6968 6983 'PhabricatorPolicyTestCase' => 'PhabricatorTestCase', 6969 6984 'PhabricatorPolicyTestObject' => array( 6970 6985 'Phobject', ··· 7265 7280 'PhabricatorSearchDocumentTypeDatasource' => 'PhabricatorTypeaheadDatasource', 7266 7281 'PhabricatorSearchEditController' => 'PhabricatorSearchBaseController', 7267 7282 'PhabricatorSearchEngine' => 'Phobject', 7283 + 'PhabricatorSearchEngineAPIMethod' => 'ConduitAPIMethod', 7284 + 'PhabricatorSearchEngineExtension' => 'Phobject', 7285 + 'PhabricatorSearchEngineExtensionModule' => 'PhabricatorConfigModule', 7268 7286 'PhabricatorSearchEngineTestCase' => 'PhabricatorTestCase', 7269 7287 'PhabricatorSearchField' => 'Phobject', 7270 7288 'PhabricatorSearchHovercardController' => 'PhabricatorSearchBaseController', ··· 7367 7385 'PhabricatorSpacesNoAccessController' => 'PhabricatorSpacesController', 7368 7386 'PhabricatorSpacesRemarkupRule' => 'PhabricatorObjectRemarkupRule', 7369 7387 'PhabricatorSpacesSchemaSpec' => 'PhabricatorConfigSchemaSpec', 7388 + 'PhabricatorSpacesSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 7370 7389 'PhabricatorSpacesSearchField' => 'PhabricatorSearchTokenizerField', 7371 7390 'PhabricatorSpacesTestCase' => 'PhabricatorTestCase', 7372 7391 'PhabricatorSpacesViewController' => 'PhabricatorSpacesController',
+31
src/applications/conduit/interface/PhabricatorConduitResultInterface.php
··· 1 + <?php 2 + 3 + interface PhabricatorConduitResultInterface 4 + extends PhabricatorPHIDInterface { 5 + 6 + public function getFieldSpecificationsForConduit(); 7 + public function getFieldValuesForConduit(); 8 + 9 + } 10 + 11 + // TEMPLATE IMPLEMENTATION ///////////////////////////////////////////////////// 12 + 13 + /* -( PhabricatorConduitResultInterface )---------------------------------- */ 14 + /* 15 + 16 + public function getFieldSpecificationsForConduit() { 17 + return array( 18 + 'name' => array( 19 + 'type' => 'string', 20 + 'description' => pht('The name of the object.'), 21 + ), 22 + ); 23 + } 24 + 25 + public function getFieldValuesForConduit() { 26 + return array( 27 + 'name' => $this->getName(), 28 + ); 29 + } 30 + 31 + */
+28
src/applications/conduit/query/ConduitResultSearchEngineExtension.php
··· 1 + <?php 2 + 3 + final class ConduitResultSearchEngineExtension 4 + extends PhabricatorSearchEngineExtension { 5 + 6 + const EXTENSIONKEY = 'conduit'; 7 + 8 + public function isExtensionEnabled() { 9 + return true; 10 + } 11 + 12 + public function getExtensionName() { 13 + return pht('Support for ConduitResultInterface'); 14 + } 15 + 16 + public function supportsObject($object) { 17 + return ($object instanceof PhabricatorConduitResultInterface); 18 + } 19 + 20 + public function getFieldSpecificationsForConduit($object) { 21 + return $object->getFieldSpecificationsForConduit(); 22 + } 23 + 24 + public function getFieldValuesForConduit($object) { 25 + return $object->getFieldValuesForConduit(); 26 + } 27 + 28 + }
+18
src/applications/paste/conduit/PasteSearchConduitAPIMethod.php
··· 1 + <?php 2 + 3 + final class PasteSearchConduitAPIMethod 4 + extends PhabricatorSearchEngineAPIMethod { 5 + 6 + public function getAPIMethodName() { 7 + return 'paste.search'; 8 + } 9 + 10 + public function newSearchEngine() { 11 + return new PhabricatorPasteSearchEngine(); 12 + } 13 + 14 + public function getMethodSummary() { 15 + return pht('Read information about pastes.'); 16 + } 17 + 18 + }
+36 -1
src/applications/paste/storage/PhabricatorPaste.php
··· 10 10 PhabricatorProjectInterface, 11 11 PhabricatorDestructibleInterface, 12 12 PhabricatorApplicationTransactionInterface, 13 - PhabricatorSpacesInterface { 13 + PhabricatorSpacesInterface, 14 + PhabricatorConduitResultInterface { 14 15 15 16 protected $title; 16 17 protected $authorPHID; ··· 248 249 249 250 public function getSpacePHID() { 250 251 return $this->spacePHID; 252 + } 253 + 254 + 255 + /* -( PhabricatorConduitResultInterface )---------------------------------- */ 256 + 257 + 258 + public function getFieldSpecificationsForConduit() { 259 + return array( 260 + 'title' => array( 261 + 'type' => 'string', 262 + 'description' => pht('The name of the object.'), 263 + ), 264 + 'authorPHID' => array( 265 + 'type' => 'phid', 266 + 'description' => pht('User PHID of the author.'), 267 + ), 268 + 'language' => array( 269 + 'type' => 'string?', 270 + 'description' => pht('Language to use for syntax highlighting.'), 271 + ), 272 + 'status' => array( 273 + 'type' => 'string', 274 + 'description' => pht('Active or archived status of the paste.'), 275 + ), 276 + ); 277 + } 278 + 279 + public function getFieldValuesForConduit() { 280 + return array( 281 + 'title' => $this->getTitle(), 282 + 'authorPHID' => $this->getAuthorPHID(), 283 + 'language' => nonempty($this->getLanguage(), null), 284 + 'status' => $this->getStatus(), 285 + ); 251 286 } 252 287 253 288 }
+43
src/applications/policy/engineextension/PhabricatorPolicySearchEngineExtension.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicySearchEngineExtension 4 + extends PhabricatorSearchEngineExtension { 5 + 6 + const EXTENSIONKEY = 'policy'; 7 + 8 + public function isExtensionEnabled() { 9 + return true; 10 + } 11 + 12 + public function getExtensionName() { 13 + return pht('Support for Policies'); 14 + } 15 + 16 + public function supportsObject($object) { 17 + return ($object instanceof PhabricatorPolicyInterface); 18 + } 19 + 20 + public function getFieldSpecificationsForConduit($object) { 21 + return array( 22 + 'policy' => array( 23 + 'type' => 'map<string, wild>', 24 + 'description' => pht( 25 + 'Map of capabilities to current policies.'), 26 + ), 27 + ); 28 + } 29 + 30 + public function getFieldValuesForConduit($object) { 31 + $capabilities = $object->getCapabilities(); 32 + 33 + $map = array(); 34 + foreach ($capabilities as $capability) { 35 + $map[$capability] = $object->getPolicy($capability); 36 + } 37 + 38 + return array( 39 + 'policy' => $map, 40 + ); 41 + } 42 + 43 + }
+193
src/applications/search/engine/PhabricatorApplicationSearchEngine.php
··· 1172 1172 return $fields; 1173 1173 } 1174 1174 1175 + public function getSearchFieldsForConduit() { 1176 + $fields = $this->buildSearchFields(); 1177 + return $fields; 1178 + } 1179 + 1180 + public function buildConduitResponse(ConduitAPIRequest $request) { 1181 + $viewer = $this->requireViewer(); 1182 + $fields = $this->buildSearchFields(); 1183 + 1184 + $query_key = $request->getValue('queryKey'); 1185 + if (!strlen($query_key)) { 1186 + $saved_query = new PhabricatorSavedQuery(); 1187 + } else if ($this->isBuiltinQuery($query_key)) { 1188 + $saved_query = $this->buildSavedQueryFromBuiltin($query_key); 1189 + } else { 1190 + $saved_query = id(new PhabricatorSavedQueryQuery()) 1191 + ->setViewer($viewer) 1192 + ->withQueryKeys(array($query_key)) 1193 + ->executeOne(); 1194 + if (!$saved_query) { 1195 + throw new Exception( 1196 + pht( 1197 + 'Query key "%s" does not correspond to a valid query.', 1198 + $query_key)); 1199 + } 1200 + } 1201 + 1202 + foreach ($fields as $field) { 1203 + $field->setViewer($viewer); 1204 + } 1205 + 1206 + $constraints = $request->getValue('constraints', array()); 1207 + 1208 + foreach ($fields as $field) { 1209 + if (!$field->getValueExistsInConduitRequest($constraints)) { 1210 + continue; 1211 + } 1212 + 1213 + $value = $field->readValueFromConduitRequest($constraints); 1214 + $saved_query->setParameter($field->getKey(), $value); 1215 + } 1216 + 1217 + $this->saveQuery($saved_query); 1218 + 1219 + 1220 + $query = $this->buildQueryFromSavedQuery($saved_query); 1221 + $pager = $this->newPagerForSavedQuery($saved_query); 1222 + 1223 + $this->setQueryOrderForConduit($query, $request); 1224 + $this->setPagerLimitForConduit($pager, $request); 1225 + $this->setPagerOffsetsForConduit($pager, $request); 1226 + 1227 + $objects = $this->executeQuery($query, $pager); 1228 + 1229 + $data = array(); 1230 + if ($objects) { 1231 + $field_extensions = $this->getConduitFieldExtensions(); 1232 + 1233 + foreach ($objects as $object) { 1234 + $data[] = $this->getObjectWireFormatForConduit( 1235 + $object, 1236 + $field_extensions); 1237 + } 1238 + } 1239 + 1240 + return array( 1241 + 'data' => $data, 1242 + 'query' => array( 1243 + 'queryKey' => $saved_query->getQueryKey(), 1244 + ), 1245 + 'cursor' => array( 1246 + 'limit' => $pager->getPageSize(), 1247 + 'after' => $pager->getNextPageID(), 1248 + 'before' => $pager->getPrevPageID(), 1249 + 'order' => $request->getValue('order'), 1250 + ), 1251 + ); 1252 + } 1253 + 1254 + public function getAllConduitFieldSpecifications() { 1255 + $extensions = $this->getConduitFieldExtensions(); 1256 + $object = $this->newQuery()->newResultObject(); 1257 + 1258 + $specifications = array(); 1259 + foreach ($extensions as $extension) { 1260 + $specifications += $extension->getFieldSpecificationsForConduit($object); 1261 + } 1262 + 1263 + return $specifications; 1264 + } 1265 + 1266 + private function getConduitFieldExtensions() { 1267 + $extensions = PhabricatorSearchEngineExtension::getAllEnabledExtensions(); 1268 + $object = $this->newQuery()->newResultObject(); 1269 + 1270 + $field_extensions = array(); 1271 + foreach ($extensions as $key => $extension) { 1272 + if ($extension->getFieldSpecificationsForConduit($object)) { 1273 + $field_extensions[$key] = $extension; 1274 + } 1275 + } 1276 + 1277 + return $field_extensions; 1278 + } 1279 + 1280 + private function setQueryOrderForConduit($query, ConduitAPIRequest $request) { 1281 + $order = $request->getValue('order'); 1282 + if ($order === null) { 1283 + return; 1284 + } 1285 + 1286 + if (is_scalar($order)) { 1287 + $query->setOrder($order); 1288 + } else { 1289 + $query->setOrderVector($order); 1290 + } 1291 + } 1292 + 1293 + private function setPagerLimitForConduit($pager, ConduitAPIRequest $request) { 1294 + $limit = $request->getValue('limit'); 1295 + 1296 + // If there's no limit specified and the query uses a weird huge page 1297 + // size, just leave it at the default gigantic page size. Otherwise, 1298 + // make sure it's between 1 and 100, inclusive. 1299 + 1300 + if ($limit === null) { 1301 + if ($pager->getPageSize() >= 0xFFFF) { 1302 + return; 1303 + } else { 1304 + $limit = 100; 1305 + } 1306 + } 1307 + 1308 + if ($limit > 100) { 1309 + throw new Exception( 1310 + pht( 1311 + 'Maximum page size for Conduit API method calls is 100, but '. 1312 + 'this call specified %s.', 1313 + $limit)); 1314 + } 1315 + 1316 + if ($limit < 1) { 1317 + throw new Exception( 1318 + pht( 1319 + 'Minimum page size for API searches is 1, but this call '. 1320 + 'specified %s.', 1321 + $limit)); 1322 + } 1323 + 1324 + $pager->setPageSize($limit); 1325 + } 1326 + 1327 + private function setPagerOffsetsForConduit( 1328 + $pager, 1329 + ConduitAPIRequest $request) { 1330 + $before_id = $request->getValue('before'); 1331 + if ($before_id !== null) { 1332 + $pager->setBeforeID($before_id); 1333 + } 1334 + 1335 + $after_id = $request->getValue('after'); 1336 + if ($after_id !== null) { 1337 + $pager->setAfterID($after_id); 1338 + } 1339 + } 1340 + 1341 + protected function getObjectWireFormatForConduit( 1342 + $object, 1343 + array $field_extensions) { 1344 + $phid = $object->getPHID(); 1345 + 1346 + return array( 1347 + 'id' => (int)$object->getID(), 1348 + 'type' => phid_get_type($phid), 1349 + 'phid' => $phid, 1350 + 'fields' => $this->getObjectWireFieldsForConduit( 1351 + $object, 1352 + $field_extensions), 1353 + ); 1354 + } 1355 + 1356 + protected function getObjectWireFieldsForConduit( 1357 + $object, 1358 + array $field_extensions) { 1359 + 1360 + $fields = array(); 1361 + foreach ($field_extensions as $extension) { 1362 + $fields += $extension->getFieldValuesForConduit($object); 1363 + } 1364 + 1365 + return $fields; 1366 + } 1367 + 1175 1368 }
+384
src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorSearchEngineAPIMethod 4 + extends ConduitAPIMethod { 5 + 6 + abstract public function newSearchEngine(); 7 + 8 + public function getApplication() { 9 + $engine = $this->newSearchEngine(); 10 + $class = $engine->getApplicationClassName(); 11 + return PhabricatorApplication::getByClass($class); 12 + } 13 + 14 + public function getMethodStatus() { 15 + return self::METHOD_STATUS_UNSTABLE; 16 + } 17 + 18 + public function getMethodStatusDescription() { 19 + return pht('ApplicationSearch methods are highly unstable.'); 20 + } 21 + 22 + final protected function defineParamTypes() { 23 + return array( 24 + 'queryKey' => 'optional string', 25 + 'constraints' => 'optional map<string, wild>', 26 + 'order' => 'optional order', 27 + ) + $this->getPagerParamTypes(); 28 + } 29 + 30 + final protected function defineReturnType() { 31 + return 'map<string, wild>'; 32 + } 33 + 34 + final protected function execute(ConduitAPIRequest $request) { 35 + $engine = $this->newSearchEngine() 36 + ->setViewer($request->getUser()); 37 + 38 + return $engine->buildConduitResponse($request); 39 + } 40 + 41 + final public function getMethodDescription() { 42 + // TODO: We don't currently have a real viewer in this method. 43 + $viewer = PhabricatorUser::getOmnipotentUser(); 44 + 45 + $engine = $this->newSearchEngine() 46 + ->setViewer($viewer); 47 + 48 + $query = $engine->newQuery(); 49 + 50 + $out = array(); 51 + 52 + $out[] = pht(<<<EOTEXT 53 + This is a standard **ApplicationSearch** method which will let you list, query, 54 + or search for objects. 55 + 56 + EOTEXT 57 + ); 58 + 59 + $out[] = pht(<<<EOTEXT 60 + Prebuilt Queries 61 + ---------------- 62 + 63 + You can use a builtin or saved query as a starting point by passing it with 64 + `queryKey`. If you don't specify a `queryKey`, the query will start with no 65 + constraints. 66 + 67 + For example, many applications have builtin queries like `"active"` or 68 + `"open"` to find only active or enabled results. To use a `queryKey`, specify 69 + it like this: 70 + 71 + ```lang=json 72 + { 73 + ... 74 + "queryKey": "active", 75 + ... 76 + } 77 + ``` 78 + 79 + These builtin and saved queries are available: 80 + EOTEXT 81 + ); 82 + 83 + $head_querykey = pht('Query Key'); 84 + $head_name = pht('Name'); 85 + $head_builtin = pht('Builtin'); 86 + 87 + $named_queries = $engine->loadAllNamedQueries(); 88 + 89 + $table = array(); 90 + $table[] = "| {$head_querykey} | {$head_name} | {$head_builtin} |"; 91 + $table[] = '|------------------|--------------|-----------------|'; 92 + foreach ($named_queries as $named_query) { 93 + $key = $named_query->getQueryKey(); 94 + $name = $named_query->getQueryName(); 95 + $builtin = $named_query->getIsBuiltin() 96 + ? pht('Builtin') 97 + : pht('Custom'); 98 + 99 + $table[] = "| `{$key}` | {$name} | {$builtin} |"; 100 + } 101 + $table = implode("\n", $table); 102 + $out[] = $table; 103 + 104 + $out[] = pht(<<<EOTEXT 105 + You can also use **any** query you run via the web UI as a starting point. You 106 + can find the key for a query by examining the URI after running a normal 107 + search. 108 + EOTEXT 109 + ); 110 + 111 + $out[] = pht(<<<EOTEXT 112 + Custom Constraints 113 + ------------------ 114 + 115 + You can add custom constraints to the basic query by passing `constraints`. 116 + This will let you filter results (for example, show only results with a 117 + certain state, status, or owner). 118 + 119 + Specify constraints like this: 120 + 121 + ```lang=json 122 + { 123 + ... 124 + "constraints": { 125 + "authorPHIDs": ["PHID-USER-1111", "PHID-USER-2222"], 126 + "statuses": ["open", "closed"] 127 + }, 128 + ... 129 + } 130 + ``` 131 + 132 + If you specify both a `queryKey` and `constraints`, the basic query 133 + configuration will be applied first as a starting point, then any additional 134 + values in `constraints` will be applied, overwriting the defaults from the 135 + original query. 136 + 137 + This API endpoint supports these constraints: 138 + EOTEXT 139 + ); 140 + 141 + $head_key = pht('Key'); 142 + $head_label = pht('Label'); 143 + $head_type = pht('Type'); 144 + $head_desc = pht('Description'); 145 + 146 + $fields = $engine->getSearchFieldsForConduit(); 147 + 148 + $table = array(); 149 + $table[] = "| {$head_key} | {$head_label} | {$head_type} | {$head_desc} |"; 150 + $table[] = '|-------------|---------------|--------------|--------------|'; 151 + foreach ($fields as $field) { 152 + $key = $field->getKey(); 153 + $label = $field->getLabel(); 154 + 155 + // TODO: Support generating and surfacing this information. 156 + $type = pht('TODO'); 157 + $description = pht('TODO'); 158 + 159 + $table[] = "| `{$key}` | **{$label}** | `{$type}` | {$description}"; 160 + } 161 + $table = implode("\n", $table); 162 + $out[] = $table; 163 + 164 + 165 + $out[] = pht(<<<EOTEXT 166 + Result Order 167 + ------------ 168 + 169 + Use `order` to choose an ordering for the results. Either specify a single 170 + key from the builtin orders (these are a set of meaningful, high-level, 171 + human-readable orders) or specify a list of low-level columns. 172 + 173 + To use a high-level order, choose a builtin order from the table below 174 + and specify it like this: 175 + 176 + ```lang=json 177 + { 178 + ... 179 + "order": "newest", 180 + ... 181 + } 182 + ``` 183 + 184 + These builtin orders are available: 185 + EOTEXT 186 + ); 187 + 188 + $head_builtin = pht('Builtin Order'); 189 + $head_description = pht('Description'); 190 + $head_columns = pht('Columns'); 191 + 192 + $orders = $query->getBuiltinOrders(); 193 + 194 + $table = array(); 195 + $table[] = "| {$head_builtin} | {$head_description} | {$head_columns} |"; 196 + $table[] = '|-----------------|---------------------|-----------------|'; 197 + foreach ($orders as $key => $order) { 198 + $name = $order['name']; 199 + $columns = implode(', ', $order['vector']); 200 + $table[] = "| `{$key}` | {$name} | {$columns} |"; 201 + } 202 + $table = implode("\n", $table); 203 + $out[] = $table; 204 + 205 + $out[] = pht(<<<EOTEXT 206 + You can choose a low-level column order instead. This is an advanced feature. 207 + 208 + In your custom order: each column may only be specified once; each column may 209 + be prefixed with "-" to invert the order; the last column must be unique; and 210 + no column other than the last may be unique. 211 + 212 + To use a low-level order, choose a sequence of columns and specify them like 213 + this: 214 + 215 + ```lang=json 216 + { 217 + ... 218 + "order": ["color", "-name", "id"], 219 + ... 220 + } 221 + ``` 222 + 223 + These low-level columns are available: 224 + EOTEXT 225 + ); 226 + 227 + $head_column = pht('Column Key'); 228 + $head_unique = pht('Unique'); 229 + 230 + $columns = $query->getOrderableColumns(); 231 + 232 + $table = array(); 233 + $table[] = "| {$head_column} | {$head_unique} |"; 234 + $table[] = '|----------------|----------------|'; 235 + foreach ($columns as $key => $column) { 236 + $unique = idx($column, 'unique') 237 + ? pht('Yes') 238 + : pht('No'); 239 + 240 + $table[] = "| `{$key}` | {$unique} |"; 241 + } 242 + $table = implode("\n", $table); 243 + $out[] = $table; 244 + 245 + 246 + $out[] = pht(<<<EOTEXT 247 + Result Format 248 + ------------- 249 + 250 + The result format is a dictionary with several fields: 251 + 252 + - `data`: Contains the actual results, as a list of dictionaries. 253 + - `query`: Details about the query which was issued. 254 + - `cursor`: Information about how to issue another query to get the next 255 + (or previous) page of results. See "Paging and Limits" below. 256 + 257 + EOTEXT 258 + ); 259 + 260 + $out[] = pht(<<<EOTEXT 261 + Fields 262 + ------ 263 + 264 + The `data` field of the result contains a list of results. Each result has 265 + some metadata and a `fields` key, which contains the primary object fields. 266 + 267 + For example, the results may look something like this: 268 + 269 + ```lang=json 270 + { 271 + ... 272 + "data": [ 273 + { 274 + "id": 123, 275 + "phid": "PHID-WXYZ-1111", 276 + "fields": { 277 + "name": "First Example Object", 278 + "authorPHID": "PHID-USER-2222" 279 + } 280 + }, 281 + { 282 + "id": 124, 283 + "phid": "PHID-WXYZ-3333", 284 + "fields": { 285 + "name": "Second Example Object", 286 + "authorPHID": "PHID-USER-4444" 287 + } 288 + }, 289 + ... 290 + ] 291 + ... 292 + } 293 + ``` 294 + 295 + This result structure is standardized across all search methods, but the 296 + available fields differ from application to application. 297 + 298 + These are the fields available on this object type: 299 + 300 + EOTEXT 301 + ); 302 + 303 + $specs = $engine->getAllConduitFieldSpecifications(); 304 + 305 + $table = array(); 306 + $table[] = "| {$head_key} | {$head_type} | {$head_description} |"; 307 + $table[] = '|-------------|--------------|---------------------|'; 308 + foreach ($specs as $key => $spec) { 309 + $type = idx($spec, 'type'); 310 + $description = idx($spec, 'description'); 311 + $table[] = "| `{$key}` | `{$type}` | {$description} |"; 312 + } 313 + $table = implode("\n", $table); 314 + $out[] = $table; 315 + 316 + $out[] = pht(<<<EOTEXT 317 + Paging and Limits 318 + ----------------- 319 + 320 + Queries are limited to returning 100 results at a time. If you want fewer 321 + results than this, you can use `limit` to specify a smaller limit. 322 + 323 + If you want more results, you'll need to make additional queries to retrieve 324 + more pages of results. 325 + 326 + The result structure contains a `cursor` key with information you'll need in 327 + order to fetch the next page. After an initial query, it will usually look 328 + something like this: 329 + 330 + ```lang=json 331 + { 332 + ... 333 + "cursor": { 334 + "limit": 100, 335 + "after": "1234", 336 + "before": null, 337 + "order": null 338 + } 339 + ... 340 + } 341 + ``` 342 + 343 + The `limit` and `order` fields are describing the effective limit and order the 344 + query was executed with, and are usually not of much interest. The `after` and 345 + `before` fields give you cursors which you can pass when making another API 346 + call in order to get the next (or previous) page of results. 347 + 348 + To get the next page of results, repeat your API call with all the same 349 + parameters as the original call, but pass the `after` cursor you received from 350 + the first call in the `after` parameter when making the second call. 351 + 352 + If you do things correctly, you should get the second page of results, and 353 + a cursor structure like this: 354 + 355 + ```lang=json 356 + { 357 + ... 358 + "cursor": { 359 + "limit": 5, 360 + "after": "4567", 361 + "before": "7890", 362 + "order": null 363 + } 364 + ... 365 + } 366 + ``` 367 + 368 + You can now continue to the third page of results by passing the new `after` 369 + cursor to the `after` parameter in your third call, or return to the previous 370 + page of results by passing the `before` cursor to the `before` parameter. This 371 + might be useful if you are rendering a web UI for a user and want to provide 372 + "Next Page" and "Previous Page" links. 373 + 374 + If `after` is `null`, there is no next page of results available. Likewise, 375 + if `before` is `null`, there are no previous results available. 376 + 377 + EOTEXT 378 + ); 379 + 380 + $out = implode("\n\n", $out); 381 + return $out; 382 + } 383 + 384 + }
+50
src/applications/search/engineextension/PhabricatorLiskSearchEngineExtension.php
··· 1 + <?php 2 + 3 + final class PhabricatorLiskSearchEngineExtension 4 + extends PhabricatorSearchEngineExtension { 5 + 6 + const EXTENSIONKEY = 'lisk'; 7 + 8 + public function isExtensionEnabled() { 9 + return true; 10 + } 11 + 12 + public function getExtensionName() { 13 + return pht('Lisk Builtin Properties'); 14 + } 15 + 16 + public function supportsObject($object) { 17 + if (!($object instanceof LiskDAO)) { 18 + return false; 19 + } 20 + 21 + if (!$object->getConfigOption(LiskDAO::CONFIG_TIMESTAMPS)) { 22 + return false; 23 + } 24 + 25 + return true; 26 + } 27 + 28 + public function getFieldSpecificationsForConduit($object) { 29 + return array( 30 + 'dateCreated' => array( 31 + 'type' => 'int', 32 + 'description' => pht( 33 + 'Epoch timestamp when the object was created.'), 34 + ), 35 + 'dateModified' => array( 36 + 'type' => 'int', 37 + 'description' => pht( 38 + 'Epoch timestamp when the object was last updated.'), 39 + ), 40 + ); 41 + } 42 + 43 + public function getFieldValuesForConduit($object) { 44 + return array( 45 + 'dateCreated' => (int)$object->getDateCreated(), 46 + 'dateModified' => (int)$object->getDateModified(), 47 + ); 48 + } 49 + 50 + }
+51
src/applications/search/engineextension/PhabricatorSearchEngineExtension.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorSearchEngineExtension extends Phobject { 4 + 5 + private $viewer; 6 + 7 + final public function getExtensionKey() { 8 + return $this->getPhobjectClassConstant('EXTENSIONKEY'); 9 + } 10 + 11 + final public function setViewer($viewer) { 12 + $this->viewer = $viewer; 13 + return $this; 14 + } 15 + 16 + final public function getViewer() { 17 + return $this->viewer; 18 + } 19 + 20 + abstract public function isExtensionEnabled(); 21 + abstract public function getExtensionName(); 22 + abstract public function supportsObject($object); 23 + 24 + public function getFieldSpecificationsForConduit($object) { 25 + return array(); 26 + } 27 + 28 + public function getFieldValuesForConduit($object) { 29 + return array(); 30 + } 31 + 32 + final public static function getAllExtensions() { 33 + return id(new PhutilClassMapQuery()) 34 + ->setAncestorClass(__CLASS__) 35 + ->setUniqueMethod('getExtensionKey') 36 + ->execute(); 37 + } 38 + 39 + final public static function getAllEnabledExtensions() { 40 + $extensions = self::getAllExtensions(); 41 + 42 + foreach ($extensions as $key => $extension) { 43 + if (!$extension->isExtensionEnabled()) { 44 + unset($extensions[$key]); 45 + } 46 + } 47 + 48 + return $extensions; 49 + } 50 + 51 + }
+52
src/applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php
··· 1 + <?php 2 + 3 + final class PhabricatorSearchEngineExtensionModule 4 + extends PhabricatorConfigModule { 5 + 6 + public function getModuleKey() { 7 + return 'searchengine'; 8 + } 9 + 10 + public function getModuleName() { 11 + return pht('SearchEngine Extensions'); 12 + } 13 + 14 + public function renderModuleStatus(AphrontRequest $request) { 15 + $viewer = $request->getViewer(); 16 + 17 + $extensions = PhabricatorSearchEngineExtension::getAllExtensions(); 18 + 19 + $rows = array(); 20 + foreach ($extensions as $extension) { 21 + $rows[] = array( 22 + $extension->getExtensionKey(), 23 + get_class($extension), 24 + $extension->getExtensionName(), 25 + $extension->isExtensionEnabled() 26 + ? pht('Yes') 27 + : pht('No'), 28 + ); 29 + } 30 + 31 + $table = id(new AphrontTableView($rows)) 32 + ->setHeaders( 33 + array( 34 + pht('Key'), 35 + pht('Class'), 36 + pht('Name'), 37 + pht('Enabled'), 38 + )) 39 + ->setColumnClasses( 40 + array( 41 + null, 42 + null, 43 + 'wide pri', 44 + null, 45 + )); 46 + 47 + return id(new PHUIObjectBoxView()) 48 + ->setHeaderText(pht('SearchEngine Extensions')) 49 + ->setTable($table); 50 + } 51 + 52 + }
+8
src/applications/search/field/PhabricatorSearchField.php
··· 205 205 return $value; 206 206 } 207 207 208 + public function getValueExistsInConduitRequest(array $constraints) { 209 + return array_key_exists($this->getKey(), $constraints); 210 + } 211 + 212 + public function readValueFromConduitRequest(array $constraints) { 213 + return idx($constraints, $this->getKey()); 214 + } 215 + 208 216 209 217 /* -( Rendering Controls )------------------------------------------------- */ 210 218
+37
src/applications/spaces/engineextension/PhabricatorSpacesSearchEngineExtension.php
··· 1 + <?php 2 + 3 + final class PhabricatorSpacesSearchEngineExtension 4 + extends PhabricatorSearchEngineExtension { 5 + 6 + const EXTENSIONKEY = 'spaces'; 7 + 8 + public function isExtensionEnabled() { 9 + return PhabricatorApplication::isClassInstalled( 10 + 'PhabricatorSpacesApplication'); 11 + } 12 + 13 + public function getExtensionName() { 14 + return pht('Support for Spaces'); 15 + } 16 + 17 + public function supportsObject($object) { 18 + return ($object instanceof PhabricatorSpacesInterface); 19 + } 20 + 21 + public function getFieldSpecificationsForConduit($object) { 22 + return array( 23 + 'spacePHID' => array( 24 + 'type' => 'phid?', 25 + 'description' => pht( 26 + 'PHID of the policy space this object is part of.'), 27 + ), 28 + ); 29 + } 30 + 31 + public function getFieldValuesForConduit($object) { 32 + return array( 33 + 'spacePHID' => $object->getSpacePHID(), 34 + ); 35 + } 36 + 37 + }