Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14022863
D14743.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
36 KB
Referenced Files
None
Subscribers
None
D14743.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -236,6 +236,7 @@
'ConduitMethodNotFoundException' => 'applications/conduit/protocol/exception/ConduitMethodNotFoundException.php',
'ConduitPingConduitAPIMethod' => 'applications/conduit/method/ConduitPingConduitAPIMethod.php',
'ConduitQueryConduitAPIMethod' => 'applications/conduit/method/ConduitQueryConduitAPIMethod.php',
+ 'ConduitResultSearchEngineExtension' => 'applications/conduit/query/ConduitResultSearchEngineExtension.php',
'ConduitSSHWorkflow' => 'applications/conduit/ssh/ConduitSSHWorkflow.php',
'ConduitTokenGarbageCollector' => 'applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php',
'ConpherenceColumnViewController' => 'applications/conpherence/controller/ConpherenceColumnViewController.php',
@@ -1557,6 +1558,7 @@
'PasteMailReceiver' => 'applications/paste/mail/PasteMailReceiver.php',
'PasteQueryConduitAPIMethod' => 'applications/paste/conduit/PasteQueryConduitAPIMethod.php',
'PasteReplyHandler' => 'applications/paste/mail/PasteReplyHandler.php',
+ 'PasteSearchConduitAPIMethod' => 'applications/paste/conduit/PasteSearchConduitAPIMethod.php',
'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php',
'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php',
'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php',
@@ -1880,6 +1882,7 @@
'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php',
'PhabricatorConduitMethodQuery' => 'applications/conduit/query/PhabricatorConduitMethodQuery.php',
'PhabricatorConduitRequestExceptionHandler' => 'aphront/handler/PhabricatorConduitRequestExceptionHandler.php',
+ 'PhabricatorConduitResultInterface' => 'applications/conduit/interface/PhabricatorConduitResultInterface.php',
'PhabricatorConduitSearchEngine' => 'applications/conduit/query/PhabricatorConduitSearchEngine.php',
'PhabricatorConduitTestCase' => '__tests__/PhabricatorConduitTestCase.php',
'PhabricatorConduitToken' => 'applications/conduit/storage/PhabricatorConduitToken.php',
@@ -2364,6 +2367,7 @@
'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php',
'PhabricatorLipsumMondrianArtist' => 'applications/lipsum/image/PhabricatorLipsumMondrianArtist.php',
'PhabricatorLiskDAO' => 'infrastructure/storage/lisk/PhabricatorLiskDAO.php',
+ 'PhabricatorLiskSearchEngineExtension' => 'applications/search/engineextension/PhabricatorLiskSearchEngineExtension.php',
'PhabricatorLiskSerializer' => 'infrastructure/storage/lisk/PhabricatorLiskSerializer.php',
'PhabricatorListFilterUIExample' => 'applications/uiexample/examples/PhabricatorListFilterUIExample.php',
'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php',
@@ -2745,6 +2749,7 @@
'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php',
'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php',
'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php',
+ 'PhabricatorPolicySearchEngineExtension' => 'applications/policy/engineextension/PhabricatorPolicySearchEngineExtension.php',
'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php',
'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php',
'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php',
@@ -2980,6 +2985,9 @@
'PhabricatorSearchDocumentTypeDatasource' => 'applications/search/typeahead/PhabricatorSearchDocumentTypeDatasource.php',
'PhabricatorSearchEditController' => 'applications/search/controller/PhabricatorSearchEditController.php',
'PhabricatorSearchEngine' => 'applications/search/engine/PhabricatorSearchEngine.php',
+ 'PhabricatorSearchEngineAPIMethod' => 'applications/search/engine/PhabricatorSearchEngineAPIMethod.php',
+ 'PhabricatorSearchEngineExtension' => 'applications/search/engineextension/PhabricatorSearchEngineExtension.php',
+ 'PhabricatorSearchEngineExtensionModule' => 'applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php',
'PhabricatorSearchEngineTestCase' => 'applications/search/engine/__tests__/PhabricatorSearchEngineTestCase.php',
'PhabricatorSearchField' => 'applications/search/field/PhabricatorSearchField.php',
'PhabricatorSearchHovercardController' => 'applications/search/controller/PhabricatorSearchHovercardController.php',
@@ -3067,6 +3075,7 @@
'PhabricatorSpacesNoAccessController' => 'applications/spaces/controller/PhabricatorSpacesNoAccessController.php',
'PhabricatorSpacesRemarkupRule' => 'applications/spaces/remarkup/PhabricatorSpacesRemarkupRule.php',
'PhabricatorSpacesSchemaSpec' => 'applications/spaces/storage/PhabricatorSpacesSchemaSpec.php',
+ 'PhabricatorSpacesSearchEngineExtension' => 'applications/spaces/engineextension/PhabricatorSpacesSearchEngineExtension.php',
'PhabricatorSpacesSearchField' => 'applications/spaces/searchfield/PhabricatorSpacesSearchField.php',
'PhabricatorSpacesTestCase' => 'applications/spaces/__tests__/PhabricatorSpacesTestCase.php',
'PhabricatorSpacesViewController' => 'applications/spaces/controller/PhabricatorSpacesViewController.php',
@@ -4063,6 +4072,7 @@
'ConduitMethodNotFoundException' => 'ConduitException',
'ConduitPingConduitAPIMethod' => 'ConduitAPIMethod',
'ConduitQueryConduitAPIMethod' => 'ConduitAPIMethod',
+ 'ConduitResultSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'ConduitSSHWorkflow' => 'PhabricatorSSHWorkflow',
'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector',
'ConpherenceColumnViewController' => 'ConpherenceController',
@@ -5577,6 +5587,7 @@
'PasteMailReceiver' => 'PhabricatorObjectMailReceiver',
'PasteQueryConduitAPIMethod' => 'PasteConduitAPIMethod',
'PasteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
+ 'PasteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability',
'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability',
'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector',
@@ -5962,6 +5973,7 @@
),
'PhabricatorConduitMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorConduitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
+ 'PhabricatorConduitResultInterface' => 'PhabricatorPHIDInterface',
'PhabricatorConduitSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorConduitTestCase' => 'PhabricatorTestCase',
'PhabricatorConduitToken' => array(
@@ -6527,6 +6539,7 @@
'PhabricatorLipsumManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorLipsumMondrianArtist' => 'PhabricatorLipsumArtist',
'PhabricatorLiskDAO' => 'LiskDAO',
+ 'PhabricatorLiskSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorLiskSerializer' => 'Phobject',
'PhabricatorListFilterUIExample' => 'PhabricatorUIExample',
'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine',
@@ -6823,6 +6836,7 @@
'PhabricatorDestructibleInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorSpacesInterface',
+ 'PhabricatorConduitResultInterface',
),
'PhabricatorPasteApplication' => 'PhabricatorApplication',
'PhabricatorPasteArchiveController' => 'PhabricatorPasteController',
@@ -6965,6 +6979,7 @@
'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorPolicyRule' => 'Phobject',
+ 'PhabricatorPolicySearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyTestObject' => array(
'Phobject',
@@ -7265,6 +7280,9 @@
'PhabricatorSearchDocumentTypeDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorSearchEditController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchEngine' => 'Phobject',
+ 'PhabricatorSearchEngineAPIMethod' => 'ConduitAPIMethod',
+ 'PhabricatorSearchEngineExtension' => 'Phobject',
+ 'PhabricatorSearchEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorSearchEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorSearchField' => 'Phobject',
'PhabricatorSearchHovercardController' => 'PhabricatorSearchBaseController',
@@ -7367,6 +7385,7 @@
'PhabricatorSpacesNoAccessController' => 'PhabricatorSpacesController',
'PhabricatorSpacesRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorSpacesSchemaSpec' => 'PhabricatorConfigSchemaSpec',
+ 'PhabricatorSpacesSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorSpacesSearchField' => 'PhabricatorSearchTokenizerField',
'PhabricatorSpacesTestCase' => 'PhabricatorTestCase',
'PhabricatorSpacesViewController' => 'PhabricatorSpacesController',
diff --git a/src/applications/conduit/interface/PhabricatorConduitResultInterface.php b/src/applications/conduit/interface/PhabricatorConduitResultInterface.php
new file mode 100644
--- /dev/null
+++ b/src/applications/conduit/interface/PhabricatorConduitResultInterface.php
@@ -0,0 +1,31 @@
+<?php
+
+interface PhabricatorConduitResultInterface
+ extends PhabricatorPHIDInterface {
+
+ public function getFieldSpecificationsForConduit();
+ public function getFieldValuesForConduit();
+
+}
+
+// TEMPLATE IMPLEMENTATION /////////////////////////////////////////////////////
+
+/* -( PhabricatorConduitResultInterface )---------------------------------- */
+/*
+
+ public function getFieldSpecificationsForConduit() {
+ return array(
+ 'name' => array(
+ 'type' => 'string',
+ 'description' => pht('The name of the object.'),
+ ),
+ );
+ }
+
+ public function getFieldValuesForConduit() {
+ return array(
+ 'name' => $this->getName(),
+ );
+ }
+
+*/
diff --git a/src/applications/conduit/query/ConduitResultSearchEngineExtension.php b/src/applications/conduit/query/ConduitResultSearchEngineExtension.php
new file mode 100644
--- /dev/null
+++ b/src/applications/conduit/query/ConduitResultSearchEngineExtension.php
@@ -0,0 +1,28 @@
+<?php
+
+final class ConduitResultSearchEngineExtension
+ extends PhabricatorSearchEngineExtension {
+
+ const EXTENSIONKEY = 'conduit';
+
+ public function isExtensionEnabled() {
+ return true;
+ }
+
+ public function getExtensionName() {
+ return pht('Support for ConduitResultInterface');
+ }
+
+ public function supportsObject($object) {
+ return ($object instanceof PhabricatorConduitResultInterface);
+ }
+
+ public function getFieldSpecificationsForConduit($object) {
+ return $object->getFieldSpecificationsForConduit();
+ }
+
+ public function getFieldValuesForConduit($object) {
+ return $object->getFieldValuesForConduit();
+ }
+
+}
diff --git a/src/applications/paste/conduit/PasteSearchConduitAPIMethod.php b/src/applications/paste/conduit/PasteSearchConduitAPIMethod.php
new file mode 100644
--- /dev/null
+++ b/src/applications/paste/conduit/PasteSearchConduitAPIMethod.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PasteSearchConduitAPIMethod
+ extends PhabricatorSearchEngineAPIMethod {
+
+ public function getAPIMethodName() {
+ return 'paste.search';
+ }
+
+ public function newSearchEngine() {
+ return new PhabricatorPasteSearchEngine();
+ }
+
+ public function getMethodSummary() {
+ return pht('Read information about pastes.');
+ }
+
+}
diff --git a/src/applications/paste/storage/PhabricatorPaste.php b/src/applications/paste/storage/PhabricatorPaste.php
--- a/src/applications/paste/storage/PhabricatorPaste.php
+++ b/src/applications/paste/storage/PhabricatorPaste.php
@@ -10,7 +10,8 @@
PhabricatorProjectInterface,
PhabricatorDestructibleInterface,
PhabricatorApplicationTransactionInterface,
- PhabricatorSpacesInterface {
+ PhabricatorSpacesInterface,
+ PhabricatorConduitResultInterface {
protected $title;
protected $authorPHID;
@@ -250,4 +251,38 @@
return $this->spacePHID;
}
+
+/* -( PhabricatorConduitResultInterface )---------------------------------- */
+
+
+ public function getFieldSpecificationsForConduit() {
+ return array(
+ 'title' => array(
+ 'type' => 'string',
+ 'description' => pht('The name of the object.'),
+ ),
+ 'authorPHID' => array(
+ 'type' => 'phid',
+ 'description' => pht('User PHID of the author.'),
+ ),
+ 'language' => array(
+ 'type' => 'string?',
+ 'description' => pht('Language to use for syntax highlighting.'),
+ ),
+ 'status' => array(
+ 'type' => 'string',
+ 'description' => pht('Active or archived status of the paste.'),
+ ),
+ );
+ }
+
+ public function getFieldValuesForConduit() {
+ return array(
+ 'title' => $this->getTitle(),
+ 'authorPHID' => $this->getAuthorPHID(),
+ 'language' => nonempty($this->getLanguage(), null),
+ 'status' => $this->getStatus(),
+ );
+ }
+
}
diff --git a/src/applications/policy/engineextension/PhabricatorPolicySearchEngineExtension.php b/src/applications/policy/engineextension/PhabricatorPolicySearchEngineExtension.php
new file mode 100644
--- /dev/null
+++ b/src/applications/policy/engineextension/PhabricatorPolicySearchEngineExtension.php
@@ -0,0 +1,43 @@
+<?php
+
+final class PhabricatorPolicySearchEngineExtension
+ extends PhabricatorSearchEngineExtension {
+
+ const EXTENSIONKEY = 'policy';
+
+ public function isExtensionEnabled() {
+ return true;
+ }
+
+ public function getExtensionName() {
+ return pht('Support for Policies');
+ }
+
+ public function supportsObject($object) {
+ return ($object instanceof PhabricatorPolicyInterface);
+ }
+
+ public function getFieldSpecificationsForConduit($object) {
+ return array(
+ 'policy' => array(
+ 'type' => 'map<string, wild>',
+ 'description' => pht(
+ 'Map of capabilities to current policies.'),
+ ),
+ );
+ }
+
+ public function getFieldValuesForConduit($object) {
+ $capabilities = $object->getCapabilities();
+
+ $map = array();
+ foreach ($capabilities as $capability) {
+ $map[$capability] = $object->getPolicy($capability);
+ }
+
+ return array(
+ 'policy' => $map,
+ );
+ }
+
+}
diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
--- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
+++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
@@ -1172,4 +1172,197 @@
return $fields;
}
+ public function getSearchFieldsForConduit() {
+ $fields = $this->buildSearchFields();
+ return $fields;
+ }
+
+ public function buildConduitResponse(ConduitAPIRequest $request) {
+ $viewer = $this->requireViewer();
+ $fields = $this->buildSearchFields();
+
+ $query_key = $request->getValue('queryKey');
+ if (!strlen($query_key)) {
+ $saved_query = new PhabricatorSavedQuery();
+ } else if ($this->isBuiltinQuery($query_key)) {
+ $saved_query = $this->buildSavedQueryFromBuiltin($query_key);
+ } else {
+ $saved_query = id(new PhabricatorSavedQueryQuery())
+ ->setViewer($viewer)
+ ->withQueryKeys(array($query_key))
+ ->executeOne();
+ if (!$saved_query) {
+ throw new Exception(
+ pht(
+ 'Query key "%s" does not correspond to a valid query.',
+ $query_key));
+ }
+ }
+
+ foreach ($fields as $field) {
+ $field->setViewer($viewer);
+ }
+
+ $constraints = $request->getValue('constraints', array());
+
+ foreach ($fields as $field) {
+ if (!$field->getValueExistsInConduitRequest($constraints)) {
+ continue;
+ }
+
+ $value = $field->readValueFromConduitRequest($constraints);
+ $saved_query->setParameter($field->getKey(), $value);
+ }
+
+ $this->saveQuery($saved_query);
+
+
+ $query = $this->buildQueryFromSavedQuery($saved_query);
+ $pager = $this->newPagerForSavedQuery($saved_query);
+
+ $this->setQueryOrderForConduit($query, $request);
+ $this->setPagerLimitForConduit($pager, $request);
+ $this->setPagerOffsetsForConduit($pager, $request);
+
+ $objects = $this->executeQuery($query, $pager);
+
+ $data = array();
+ if ($objects) {
+ $field_extensions = $this->getConduitFieldExtensions();
+
+ foreach ($objects as $object) {
+ $data[] = $this->getObjectWireFormatForConduit(
+ $object,
+ $field_extensions);
+ }
+ }
+
+ return array(
+ 'data' => $data,
+ 'query' => array(
+ 'queryKey' => $saved_query->getQueryKey(),
+ ),
+ 'cursor' => array(
+ 'limit' => $pager->getPageSize(),
+ 'after' => $pager->getNextPageID(),
+ 'before' => $pager->getPrevPageID(),
+ 'order' => $request->getValue('order'),
+ ),
+ );
+ }
+
+ public function getAllConduitFieldSpecifications() {
+ $extensions = $this->getConduitFieldExtensions();
+ $object = $this->newQuery()->newResultObject();
+
+ $specifications = array();
+ foreach ($extensions as $extension) {
+ $specifications += $extension->getFieldSpecificationsForConduit($object);
+ }
+
+ return $specifications;
+ }
+
+ private function getConduitFieldExtensions() {
+ $extensions = PhabricatorSearchEngineExtension::getAllEnabledExtensions();
+ $object = $this->newQuery()->newResultObject();
+
+ $field_extensions = array();
+ foreach ($extensions as $key => $extension) {
+ if ($extension->getFieldSpecificationsForConduit($object)) {
+ $field_extensions[$key] = $extension;
+ }
+ }
+
+ return $field_extensions;
+ }
+
+ private function setQueryOrderForConduit($query, ConduitAPIRequest $request) {
+ $order = $request->getValue('order');
+ if ($order === null) {
+ return;
+ }
+
+ if (is_scalar($order)) {
+ $query->setOrder($order);
+ } else {
+ $query->setOrderVector($order);
+ }
+ }
+
+ private function setPagerLimitForConduit($pager, ConduitAPIRequest $request) {
+ $limit = $request->getValue('limit');
+
+ // If there's no limit specified and the query uses a weird huge page
+ // size, just leave it at the default gigantic page size. Otherwise,
+ // make sure it's between 1 and 100, inclusive.
+
+ if ($limit === null) {
+ if ($pager->getPageSize() >= 0xFFFF) {
+ return;
+ } else {
+ $limit = 100;
+ }
+ }
+
+ if ($limit > 100) {
+ throw new Exception(
+ pht(
+ 'Maximum page size for Conduit API method calls is 100, but '.
+ 'this call specified %s.',
+ $limit));
+ }
+
+ if ($limit < 1) {
+ throw new Exception(
+ pht(
+ 'Minimum page size for API searches is 1, but this call '.
+ 'specified %s.',
+ $limit));
+ }
+
+ $pager->setPageSize($limit);
+ }
+
+ private function setPagerOffsetsForConduit(
+ $pager,
+ ConduitAPIRequest $request) {
+ $before_id = $request->getValue('before');
+ if ($before_id !== null) {
+ $pager->setBeforeID($before_id);
+ }
+
+ $after_id = $request->getValue('after');
+ if ($after_id !== null) {
+ $pager->setAfterID($after_id);
+ }
+ }
+
+ protected function getObjectWireFormatForConduit(
+ $object,
+ array $field_extensions) {
+ $phid = $object->getPHID();
+
+ return array(
+ 'id' => (int)$object->getID(),
+ 'type' => phid_get_type($phid),
+ 'phid' => $phid,
+ 'fields' => $this->getObjectWireFieldsForConduit(
+ $object,
+ $field_extensions),
+ );
+ }
+
+ protected function getObjectWireFieldsForConduit(
+ $object,
+ array $field_extensions) {
+
+ $fields = array();
+ foreach ($field_extensions as $extension) {
+ $fields += $extension->getFieldValuesForConduit($object);
+ }
+
+ return $fields;
+ }
+
}
diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
new file mode 100644
--- /dev/null
+++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
@@ -0,0 +1,384 @@
+<?php
+
+abstract class PhabricatorSearchEngineAPIMethod
+ extends ConduitAPIMethod {
+
+ abstract public function newSearchEngine();
+
+ public function getApplication() {
+ $engine = $this->newSearchEngine();
+ $class = $engine->getApplicationClassName();
+ return PhabricatorApplication::getByClass($class);
+ }
+
+ public function getMethodStatus() {
+ return self::METHOD_STATUS_UNSTABLE;
+ }
+
+ public function getMethodStatusDescription() {
+ return pht('ApplicationSearch methods are highly unstable.');
+ }
+
+ final protected function defineParamTypes() {
+ return array(
+ 'queryKey' => 'optional string',
+ 'constraints' => 'optional map<string, wild>',
+ 'order' => 'optional order',
+ ) + $this->getPagerParamTypes();
+ }
+
+ final protected function defineReturnType() {
+ return 'map<string, wild>';
+ }
+
+ final protected function execute(ConduitAPIRequest $request) {
+ $engine = $this->newSearchEngine()
+ ->setViewer($request->getUser());
+
+ return $engine->buildConduitResponse($request);
+ }
+
+ final public function getMethodDescription() {
+ // TODO: We don't currently have a real viewer in this method.
+ $viewer = PhabricatorUser::getOmnipotentUser();
+
+ $engine = $this->newSearchEngine()
+ ->setViewer($viewer);
+
+ $query = $engine->newQuery();
+
+ $out = array();
+
+ $out[] = pht(<<<EOTEXT
+This is a standard **ApplicationSearch** method which will let you list, query,
+or search for objects.
+
+EOTEXT
+ );
+
+ $out[] = pht(<<<EOTEXT
+Prebuilt Queries
+----------------
+
+You can use a builtin or saved query as a starting point by passing it with
+`queryKey`. If you don't specify a `queryKey`, the query will start with no
+constraints.
+
+For example, many applications have builtin queries like `"active"` or
+`"open"` to find only active or enabled results. To use a `queryKey`, specify
+it like this:
+
+```lang=json
+{
+ ...
+ "queryKey": "active",
+ ...
+}
+```
+
+These builtin and saved queries are available:
+EOTEXT
+ );
+
+ $head_querykey = pht('Query Key');
+ $head_name = pht('Name');
+ $head_builtin = pht('Builtin');
+
+ $named_queries = $engine->loadAllNamedQueries();
+
+ $table = array();
+ $table[] = "| {$head_querykey} | {$head_name} | {$head_builtin} |";
+ $table[] = '|------------------|--------------|-----------------|';
+ foreach ($named_queries as $named_query) {
+ $key = $named_query->getQueryKey();
+ $name = $named_query->getQueryName();
+ $builtin = $named_query->getIsBuiltin()
+ ? pht('Builtin')
+ : pht('Custom');
+
+ $table[] = "| `{$key}` | {$name} | {$builtin} |";
+ }
+ $table = implode("\n", $table);
+ $out[] = $table;
+
+ $out[] = pht(<<<EOTEXT
+You can also use **any** query you run via the web UI as a starting point. You
+can find the key for a query by examining the URI after running a normal
+search.
+EOTEXT
+ );
+
+ $out[] = pht(<<<EOTEXT
+Custom Constraints
+------------------
+
+You can add custom constraints to the basic query by passing `constraints`.
+This will let you filter results (for example, show only results with a
+certain state, status, or owner).
+
+Specify constraints like this:
+
+```lang=json
+{
+ ...
+ "constraints": {
+ "authorPHIDs": ["PHID-USER-1111", "PHID-USER-2222"],
+ "statuses": ["open", "closed"]
+ },
+ ...
+}
+```
+
+If you specify both a `queryKey` and `constraints`, the basic query
+configuration will be applied first as a starting point, then any additional
+values in `constraints` will be applied, overwriting the defaults from the
+original query.
+
+This API endpoint supports these constraints:
+EOTEXT
+ );
+
+ $head_key = pht('Key');
+ $head_label = pht('Label');
+ $head_type = pht('Type');
+ $head_desc = pht('Description');
+
+ $fields = $engine->getSearchFieldsForConduit();
+
+ $table = array();
+ $table[] = "| {$head_key} | {$head_label} | {$head_type} | {$head_desc} |";
+ $table[] = '|-------------|---------------|--------------|--------------|';
+ foreach ($fields as $field) {
+ $key = $field->getKey();
+ $label = $field->getLabel();
+
+ // TODO: Support generating and surfacing this information.
+ $type = pht('TODO');
+ $description = pht('TODO');
+
+ $table[] = "| `{$key}` | **{$label}** | `{$type}` | {$description}";
+ }
+ $table = implode("\n", $table);
+ $out[] = $table;
+
+
+ $out[] = pht(<<<EOTEXT
+Result Order
+------------
+
+Use `order` to choose an ordering for the results. Either specify a single
+key from the builtin orders (these are a set of meaningful, high-level,
+human-readable orders) or specify a list of low-level columns.
+
+To use a high-level order, choose a builtin order from the table below
+and specify it like this:
+
+```lang=json
+{
+ ...
+ "order": "newest",
+ ...
+}
+```
+
+These builtin orders are available:
+EOTEXT
+ );
+
+ $head_builtin = pht('Builtin Order');
+ $head_description = pht('Description');
+ $head_columns = pht('Columns');
+
+ $orders = $query->getBuiltinOrders();
+
+ $table = array();
+ $table[] = "| {$head_builtin} | {$head_description} | {$head_columns} |";
+ $table[] = '|-----------------|---------------------|-----------------|';
+ foreach ($orders as $key => $order) {
+ $name = $order['name'];
+ $columns = implode(', ', $order['vector']);
+ $table[] = "| `{$key}` | {$name} | {$columns} |";
+ }
+ $table = implode("\n", $table);
+ $out[] = $table;
+
+ $out[] = pht(<<<EOTEXT
+You can choose a low-level column order instead. This is an advanced feature.
+
+In your custom order: each column may only be specified once; each column may
+be prefixed with "-" to invert the order; the last column must be unique; and
+no column other than the last may be unique.
+
+To use a low-level order, choose a sequence of columns and specify them like
+this:
+
+```lang=json
+{
+ ...
+ "order": ["color", "-name", "id"],
+ ...
+}
+```
+
+These low-level columns are available:
+EOTEXT
+ );
+
+ $head_column = pht('Column Key');
+ $head_unique = pht('Unique');
+
+ $columns = $query->getOrderableColumns();
+
+ $table = array();
+ $table[] = "| {$head_column} | {$head_unique} |";
+ $table[] = '|----------------|----------------|';
+ foreach ($columns as $key => $column) {
+ $unique = idx($column, 'unique')
+ ? pht('Yes')
+ : pht('No');
+
+ $table[] = "| `{$key}` | {$unique} |";
+ }
+ $table = implode("\n", $table);
+ $out[] = $table;
+
+
+ $out[] = pht(<<<EOTEXT
+Result Format
+-------------
+
+The result format is a dictionary with several fields:
+
+ - `data`: Contains the actual results, as a list of dictionaries.
+ - `query`: Details about the query which was issued.
+ - `cursor`: Information about how to issue another query to get the next
+ (or previous) page of results. See "Paging and Limits" below.
+
+EOTEXT
+ );
+
+ $out[] = pht(<<<EOTEXT
+Fields
+------
+
+The `data` field of the result contains a list of results. Each result has
+some metadata and a `fields` key, which contains the primary object fields.
+
+For example, the results may look something like this:
+
+```lang=json
+{
+ ...
+ "data": [
+ {
+ "id": 123,
+ "phid": "PHID-WXYZ-1111",
+ "fields": {
+ "name": "First Example Object",
+ "authorPHID": "PHID-USER-2222"
+ }
+ },
+ {
+ "id": 124,
+ "phid": "PHID-WXYZ-3333",
+ "fields": {
+ "name": "Second Example Object",
+ "authorPHID": "PHID-USER-4444"
+ }
+ },
+ ...
+ ]
+ ...
+}
+```
+
+This result structure is standardized across all search methods, but the
+available fields differ from application to application.
+
+These are the fields available on this object type:
+
+EOTEXT
+ );
+
+ $specs = $engine->getAllConduitFieldSpecifications();
+
+ $table = array();
+ $table[] = "| {$head_key} | {$head_type} | {$head_description} |";
+ $table[] = '|-------------|--------------|---------------------|';
+ foreach ($specs as $key => $spec) {
+ $type = idx($spec, 'type');
+ $description = idx($spec, 'description');
+ $table[] = "| `{$key}` | `{$type}` | {$description} |";
+ }
+ $table = implode("\n", $table);
+ $out[] = $table;
+
+ $out[] = pht(<<<EOTEXT
+Paging and Limits
+-----------------
+
+Queries are limited to returning 100 results at a time. If you want fewer
+results than this, you can use `limit` to specify a smaller limit.
+
+If you want more results, you'll need to make additional queries to retrieve
+more pages of results.
+
+The result structure contains a `cursor` key with information you'll need in
+order to fetch the next page. After an initial query, it will usually look
+something like this:
+
+```lang=json
+{
+ ...
+ "cursor": {
+ "limit": 100,
+ "after": "1234",
+ "before": null,
+ "order": null
+ }
+ ...
+}
+```
+
+The `limit` and `order` fields are describing the effective limit and order the
+query was executed with, and are usually not of much interest. The `after` and
+`before` fields give you cursors which you can pass when making another API
+call in order to get the next (or previous) page of results.
+
+To get the next page of results, repeat your API call with all the same
+parameters as the original call, but pass the `after` cursor you received from
+the first call in the `after` parameter when making the second call.
+
+If you do things correctly, you should get the second page of results, and
+a cursor structure like this:
+
+```lang=json
+{
+ ...
+ "cursor": {
+ "limit": 5,
+ "after": "4567",
+ "before": "7890",
+ "order": null
+ }
+ ...
+}
+```
+
+You can now continue to the third page of results by passing the new `after`
+cursor to the `after` parameter in your third call, or return to the previous
+page of results by passing the `before` cursor to the `before` parameter. This
+might be useful if you are rendering a web UI for a user and want to provide
+"Next Page" and "Previous Page" links.
+
+If `after` is `null`, there is no next page of results available. Likewise,
+if `before` is `null`, there are no previous results available.
+
+EOTEXT
+ );
+
+ $out = implode("\n\n", $out);
+ return $out;
+ }
+
+}
diff --git a/src/applications/search/engineextension/PhabricatorLiskSearchEngineExtension.php b/src/applications/search/engineextension/PhabricatorLiskSearchEngineExtension.php
new file mode 100644
--- /dev/null
+++ b/src/applications/search/engineextension/PhabricatorLiskSearchEngineExtension.php
@@ -0,0 +1,50 @@
+<?php
+
+final class PhabricatorLiskSearchEngineExtension
+ extends PhabricatorSearchEngineExtension {
+
+ const EXTENSIONKEY = 'lisk';
+
+ public function isExtensionEnabled() {
+ return true;
+ }
+
+ public function getExtensionName() {
+ return pht('Lisk Builtin Properties');
+ }
+
+ public function supportsObject($object) {
+ if (!($object instanceof LiskDAO)) {
+ return false;
+ }
+
+ if (!$object->getConfigOption(LiskDAO::CONFIG_TIMESTAMPS)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function getFieldSpecificationsForConduit($object) {
+ return array(
+ 'dateCreated' => array(
+ 'type' => 'int',
+ 'description' => pht(
+ 'Epoch timestamp when the object was created.'),
+ ),
+ 'dateModified' => array(
+ 'type' => 'int',
+ 'description' => pht(
+ 'Epoch timestamp when the object was last updated.'),
+ ),
+ );
+ }
+
+ public function getFieldValuesForConduit($object) {
+ return array(
+ 'dateCreated' => (int)$object->getDateCreated(),
+ 'dateModified' => (int)$object->getDateModified(),
+ );
+ }
+
+}
diff --git a/src/applications/search/engineextension/PhabricatorSearchEngineExtension.php b/src/applications/search/engineextension/PhabricatorSearchEngineExtension.php
new file mode 100644
--- /dev/null
+++ b/src/applications/search/engineextension/PhabricatorSearchEngineExtension.php
@@ -0,0 +1,51 @@
+<?php
+
+abstract class PhabricatorSearchEngineExtension extends Phobject {
+
+ private $viewer;
+
+ final public function getExtensionKey() {
+ return $this->getPhobjectClassConstant('EXTENSIONKEY');
+ }
+
+ final public function setViewer($viewer) {
+ $this->viewer = $viewer;
+ return $this;
+ }
+
+ final public function getViewer() {
+ return $this->viewer;
+ }
+
+ abstract public function isExtensionEnabled();
+ abstract public function getExtensionName();
+ abstract public function supportsObject($object);
+
+ public function getFieldSpecificationsForConduit($object) {
+ return array();
+ }
+
+ public function getFieldValuesForConduit($object) {
+ return array();
+ }
+
+ final public static function getAllExtensions() {
+ return id(new PhutilClassMapQuery())
+ ->setAncestorClass(__CLASS__)
+ ->setUniqueMethod('getExtensionKey')
+ ->execute();
+ }
+
+ final public static function getAllEnabledExtensions() {
+ $extensions = self::getAllExtensions();
+
+ foreach ($extensions as $key => $extension) {
+ if (!$extension->isExtensionEnabled()) {
+ unset($extensions[$key]);
+ }
+ }
+
+ return $extensions;
+ }
+
+}
diff --git a/src/applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php b/src/applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php
new file mode 100644
--- /dev/null
+++ b/src/applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php
@@ -0,0 +1,52 @@
+<?php
+
+final class PhabricatorSearchEngineExtensionModule
+ extends PhabricatorConfigModule {
+
+ public function getModuleKey() {
+ return 'searchengine';
+ }
+
+ public function getModuleName() {
+ return pht('SearchEngine Extensions');
+ }
+
+ public function renderModuleStatus(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+
+ $extensions = PhabricatorSearchEngineExtension::getAllExtensions();
+
+ $rows = array();
+ foreach ($extensions as $extension) {
+ $rows[] = array(
+ $extension->getExtensionKey(),
+ get_class($extension),
+ $extension->getExtensionName(),
+ $extension->isExtensionEnabled()
+ ? pht('Yes')
+ : pht('No'),
+ );
+ }
+
+ $table = id(new AphrontTableView($rows))
+ ->setHeaders(
+ array(
+ pht('Key'),
+ pht('Class'),
+ pht('Name'),
+ pht('Enabled'),
+ ))
+ ->setColumnClasses(
+ array(
+ null,
+ null,
+ 'wide pri',
+ null,
+ ));
+
+ return id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('SearchEngine Extensions'))
+ ->setTable($table);
+ }
+
+}
diff --git a/src/applications/search/field/PhabricatorSearchField.php b/src/applications/search/field/PhabricatorSearchField.php
--- a/src/applications/search/field/PhabricatorSearchField.php
+++ b/src/applications/search/field/PhabricatorSearchField.php
@@ -205,6 +205,14 @@
return $value;
}
+ public function getValueExistsInConduitRequest(array $constraints) {
+ return array_key_exists($this->getKey(), $constraints);
+ }
+
+ public function readValueFromConduitRequest(array $constraints) {
+ return idx($constraints, $this->getKey());
+ }
+
/* -( Rendering Controls )------------------------------------------------- */
diff --git a/src/applications/spaces/engineextension/PhabricatorSpacesSearchEngineExtension.php b/src/applications/spaces/engineextension/PhabricatorSpacesSearchEngineExtension.php
new file mode 100644
--- /dev/null
+++ b/src/applications/spaces/engineextension/PhabricatorSpacesSearchEngineExtension.php
@@ -0,0 +1,37 @@
+<?php
+
+final class PhabricatorSpacesSearchEngineExtension
+ extends PhabricatorSearchEngineExtension {
+
+ const EXTENSIONKEY = 'spaces';
+
+ public function isExtensionEnabled() {
+ return PhabricatorApplication::isClassInstalled(
+ 'PhabricatorSpacesApplication');
+ }
+
+ public function getExtensionName() {
+ return pht('Support for Spaces');
+ }
+
+ public function supportsObject($object) {
+ return ($object instanceof PhabricatorSpacesInterface);
+ }
+
+ public function getFieldSpecificationsForConduit($object) {
+ return array(
+ 'spacePHID' => array(
+ 'type' => 'phid?',
+ 'description' => pht(
+ 'PHID of the policy space this object is part of.'),
+ ),
+ );
+ }
+
+ public function getFieldValuesForConduit($object) {
+ return array(
+ 'spacePHID' => $object->getSpacePHID(),
+ );
+ }
+
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Nov 7, 8:16 PM (1 w, 4 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/6w/ri/duurchf4khu7icwg
Default Alt Text
D14743.diff (36 KB)
Attached To
Mode
D14743: Implement a rough initial version of ApplicationSearch-driven Conduit read endpoints
Attached
Detach File
Event Timeline
Log In to Comment