1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 08:52:39 +01:00

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
This commit is contained in:
epriestley 2015-12-11 08:45:56 -08:00
parent ab7d3caa00
commit 4ec6990ca7
13 changed files with 950 additions and 1 deletions

View file

@ -236,6 +236,7 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'ConduitMethodNotFoundException' => 'ConduitException',
'ConduitPingConduitAPIMethod' => 'ConduitAPIMethod',
'ConduitQueryConduitAPIMethod' => 'ConduitAPIMethod',
'ConduitResultSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'ConduitSSHWorkflow' => 'PhabricatorSSHWorkflow',
'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector',
'ConpherenceColumnViewController' => 'ConpherenceController',
@ -5577,6 +5587,7 @@ phutil_register_library_map(array(
'PasteMailReceiver' => 'PhabricatorObjectMailReceiver',
'PasteQueryConduitAPIMethod' => 'PasteConduitAPIMethod',
'PasteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PasteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability',
'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability',
'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector',
@ -5962,6 +5973,7 @@ phutil_register_library_map(array(
),
'PhabricatorConduitMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorConduitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorConduitResultInterface' => 'PhabricatorPHIDInterface',
'PhabricatorConduitSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorConduitTestCase' => 'PhabricatorTestCase',
'PhabricatorConduitToken' => array(
@ -6527,6 +6539,7 @@ phutil_register_library_map(array(
'PhabricatorLipsumManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorLipsumMondrianArtist' => 'PhabricatorLipsumArtist',
'PhabricatorLiskDAO' => 'LiskDAO',
'PhabricatorLiskSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorLiskSerializer' => 'Phobject',
'PhabricatorListFilterUIExample' => 'PhabricatorUIExample',
'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine',
@ -6823,6 +6836,7 @@ phutil_register_library_map(array(
'PhabricatorDestructibleInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorSpacesInterface',
'PhabricatorConduitResultInterface',
),
'PhabricatorPasteApplication' => 'PhabricatorApplication',
'PhabricatorPasteArchiveController' => 'PhabricatorPasteController',
@ -6965,6 +6979,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorPolicyRule' => 'Phobject',
'PhabricatorPolicySearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyTestObject' => array(
'Phobject',
@ -7265,6 +7280,9 @@ phutil_register_library_map(array(
'PhabricatorSearchDocumentTypeDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorSearchEditController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchEngine' => 'Phobject',
'PhabricatorSearchEngineAPIMethod' => 'ConduitAPIMethod',
'PhabricatorSearchEngineExtension' => 'Phobject',
'PhabricatorSearchEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorSearchEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorSearchField' => 'Phobject',
'PhabricatorSearchHovercardController' => 'PhabricatorSearchBaseController',
@ -7367,6 +7385,7 @@ phutil_register_library_map(array(
'PhabricatorSpacesNoAccessController' => 'PhabricatorSpacesController',
'PhabricatorSpacesRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorSpacesSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorSpacesSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorSpacesSearchField' => 'PhabricatorSearchTokenizerField',
'PhabricatorSpacesTestCase' => 'PhabricatorTestCase',
'PhabricatorSpacesViewController' => 'PhabricatorSpacesController',

View file

@ -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(),
);
}
*/

View file

@ -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();
}
}

View file

@ -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.');
}
}

View file

@ -10,7 +10,8 @@ final class PhabricatorPaste extends PhabricatorPasteDAO
PhabricatorProjectInterface,
PhabricatorDestructibleInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorSpacesInterface {
PhabricatorSpacesInterface,
PhabricatorConduitResultInterface {
protected $title;
protected $authorPHID;
@ -250,4 +251,38 @@ final class PhabricatorPaste extends PhabricatorPasteDAO
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(),
);
}
}

View file

@ -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,
);
}
}

View file

@ -1172,4 +1172,197 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
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;
}
}

View file

@ -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;
}
}

View file

@ -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(),
);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -205,6 +205,14 @@ abstract class PhabricatorSearchField extends Phobject {
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 )------------------------------------------------- */

View file

@ -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(),
);
}
}