Page MenuHomePhabricator

D14743.diff
No OneTemporary

D14743.diff

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

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)

Event Timeline