Changeset View
Changeset View
Standalone View
Standalone View
src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
Show All 33 Lines | abstract class PhabricatorSearchEngineAPIMethod | ||||
final protected function execute(ConduitAPIRequest $request) { | final protected function execute(ConduitAPIRequest $request) { | ||||
$engine = $this->newSearchEngine() | $engine = $this->newSearchEngine() | ||||
->setViewer($request->getUser()); | ->setViewer($request->getUser()); | ||||
return $engine->buildConduitResponse($request); | return $engine->buildConduitResponse($request); | ||||
} | } | ||||
final public function getMethodDescription() { | final public function getMethodDescription() { | ||||
return pht( | |||||
'This is a standard **ApplicationSearch** method which will let you '. | |||||
'list, query, or search for objects.'); | |||||
} | |||||
final public function getMethodDocumentation() { | |||||
$viewer = $this->getViewer(); | $viewer = $this->getViewer(); | ||||
$engine = $this->newSearchEngine() | $engine = $this->newSearchEngine() | ||||
->setViewer($viewer); | ->setViewer($viewer); | ||||
$query = $engine->newQuery(); | $query = $engine->newQuery(); | ||||
$out = array(); | $out = array(); | ||||
$out[] = pht(<<<EOTEXT | $out[] = $this->buildQueriesBox($engine); | ||||
This is a standard **ApplicationSearch** method which will let you list, query, | $out[] = $this->buildConstraintsBox($engine); | ||||
or search for objects. | $out[] = $this->buildOrderBox($engine, $query); | ||||
$out[] = $this->buildFieldsBox($engine); | |||||
$out[] = $this->buildPagingBox($engine); | |||||
EOTEXT | return $out; | ||||
); | } | ||||
$out[] = pht(<<<EOTEXT | private function buildQueriesBox( | ||||
Prebuilt Queries | PhabricatorApplicationSearchEngine $engine) { | ||||
---------------- | $viewer = $this->getViewer(); | ||||
You can use a builtin or saved query as a starting point by passing it with | $info = pht(<<<EOTEXT | ||||
`queryKey`. If you don't specify a `queryKey`, the query will start with no | You can choose a builtin or saved query as a starting point for filtering | ||||
constraints. | results by selecting 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 | For example, many applications have builtin queries like `"active"` or | ||||
`"open"` to find only active or enabled results. To use a `queryKey`, specify | `"open"` to find only active or enabled results. To use a `queryKey`, specify | ||||
it like this: | it like this: | ||||
```lang=json | ```lang=json, name="Selecting a Builtin Query" | ||||
{ | { | ||||
... | ... | ||||
"queryKey": "active", | "queryKey": "active", | ||||
... | ... | ||||
} | } | ||||
``` | ``` | ||||
The table below shows the keys to use to select builtin queries and your | |||||
saved queries, but 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. | |||||
You can use these keys to select builtin queries and your configured saved | You can use these keys to select builtin queries and your configured saved | ||||
queries: | queries: | ||||
EOTEXT | EOTEXT | ||||
); | ); | ||||
$head_querykey = pht('Query Key'); | |||||
$head_name = pht('Name'); | |||||
$head_builtin = pht('Builtin'); | |||||
$named_queries = $engine->loadAllNamedQueries(); | $named_queries = $engine->loadAllNamedQueries(); | ||||
$table = array(); | $rows = array(); | ||||
$table[] = "| {$head_querykey} | {$head_name} | {$head_builtin} |"; | |||||
$table[] = '|------------------|--------------|-----------------|'; | |||||
foreach ($named_queries as $named_query) { | foreach ($named_queries as $named_query) { | ||||
$key = $named_query->getQueryKey(); | |||||
$name = $named_query->getQueryName(); | |||||
$builtin = $named_query->getIsBuiltin() | $builtin = $named_query->getIsBuiltin() | ||||
? pht('Builtin') | ? pht('Builtin') | ||||
: pht('Custom'); | : pht('Custom'); | ||||
$table[] = "| `{$key}` | {$name} | {$builtin} |"; | $rows[] = array( | ||||
} | $named_query->getQueryKey(), | ||||
$table = implode("\n", $table); | $named_query->getQueryName(), | ||||
$out[] = $table; | $builtin, | ||||
$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 | $table = id(new AphrontTableView($rows)) | ||||
Custom Constraints | ->setHeaders( | ||||
------------------ | array( | ||||
pht('Query Key'), | |||||
You can add custom constraints to the basic query by passing `constraints`. | pht('Name'), | ||||
This will let you filter results (for example, show only results with a | pht('Builtin'), | ||||
certain state, status, or owner). | )) | ||||
->setColumnClasses( | |||||
array( | |||||
'prewrap', | |||||
'pri wide', | |||||
null, | |||||
)); | |||||
return id(new PHUIObjectBoxView()) | |||||
->setHeaderText(pht('Builtin and Saved Queries')) | |||||
->setCollapsed(true) | |||||
->appendChild($this->buildRemarkup($info)) | |||||
->appendChild($table); | |||||
} | |||||
private function buildConstraintsBox( | |||||
PhabricatorApplicationSearchEngine $engine) { | |||||
$info = pht(<<<EOTEXT | |||||
You can apply custom constraints by passing a dictionary in `constraints`. | |||||
This will let you search for specific sets of results (for example, you may | |||||
want show only results with a certain state, status, or owner). | |||||
If you specify both a `queryKey` and `constraints`, the builtin or saved query | |||||
will be applied first as a starting point, then any additional values in | |||||
`constraints` will be applied, overwriting the defaults from the original query. | |||||
Specify constraints like this: | Specify constraints like this: | ||||
```lang=json | ```lang=json, name="Example Custom Constraints" | ||||
{ | { | ||||
... | ... | ||||
"constraints": { | "constraints": { | ||||
"authorPHIDs": ["PHID-USER-1111", "PHID-USER-2222"], | "authors": ["PHID-USER-1111", "PHID-USER-2222"], | ||||
"statuses": ["open", "closed"] | "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: | This API endpoint supports these constraints: | ||||
EOTEXT | EOTEXT | ||||
); | ); | ||||
$head_key = pht('Key'); | |||||
$head_label = pht('Label'); | |||||
$head_type = pht('Type'); | |||||
$head_desc = pht('Description'); | |||||
$desc_ids = pht('Search for specific objects by ID.'); | |||||
$desc_phids = pht('Search for specific objects by PHID.'); | |||||
$fields = $engine->getSearchFieldsForConduit(); | $fields = $engine->getSearchFieldsForConduit(); | ||||
$table = array(); | $rows = array(); | ||||
$table[] = "| {$head_key} | {$head_label} | {$head_type} | {$head_desc} |"; | |||||
$table[] = '|-------------|---------------|--------------|--------------|'; | |||||
$table[] = "| `ids` | **IDs** | `list<int>` | {$desc_ids} |"; | |||||
$table[] = "| `phids` | **PHIDs** | `list<phid>` | {$desc_phids} |"; | |||||
foreach ($fields as $field) { | foreach ($fields as $field) { | ||||
$key = $field->getConduitKey(); | $key = $field->getConduitKey(); | ||||
$label = $field->getLabel(); | $label = $field->getLabel(); | ||||
$type_object = $field->getConduitParameterType(); | $type_object = $field->getConduitParameterType(); | ||||
if ($type_object) { | if ($type_object) { | ||||
$type = '`'.$type_object->getTypeName().'`'; | $type = $type_object->getTypeName(); | ||||
$description = $field->getDescription(); | $description = $field->getDescription(); | ||||
} else { | } else { | ||||
$type = ''; | $type = null; | ||||
$description = '//'.pht('Not Supported').'//'; | $description = phutil_tag('em', array(), pht('Not supported.')); | ||||
} | } | ||||
$table[] = "| `{$key}` | **{$label}** | {$type} | {$description}"; | $rows[] = array( | ||||
$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 | $table = id(new AphrontTableView($rows)) | ||||
key from the builtin orders (these are a set of meaningful, high-level, | ->setHeaders( | ||||
human-readable orders) or specify a list of low-level columns. | array( | ||||
pht('Key'), | |||||
pht('Label'), | |||||
pht('Type'), | |||||
pht('Description'), | |||||
)) | |||||
->setColumnClasses( | |||||
array( | |||||
'prewrap', | |||||
'pri', | |||||
'prewrap', | |||||
'wide', | |||||
)); | |||||
return id(new PHUIObjectBoxView()) | |||||
->setHeaderText(pht('Custom Query Constraints')) | |||||
->setCollapsed(true) | |||||
->appendChild($this->buildRemarkup($info)) | |||||
->appendChild($table); | |||||
} | |||||
private function buildOrderBox( | |||||
PhabricatorApplicationSearchEngine $engine, | |||||
$query) { | |||||
$orders_info = pht(<<<EOTEXT | |||||
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 custom list of | |||||
low-level columns. | |||||
To use a high-level order, choose a builtin order from the table below | To use a high-level order, choose a builtin order from the table below | ||||
and specify it like this: | and specify it like this: | ||||
```lang=json | ```lang=json, name="Choosing a Result Order" | ||||
{ | { | ||||
... | ... | ||||
"order": "newest", | "order": "newest", | ||||
... | ... | ||||
} | } | ||||
``` | ``` | ||||
These builtin orders are available: | These builtin orders are available: | ||||
EOTEXT | EOTEXT | ||||
); | ); | ||||
$head_builtin = pht('Builtin Order'); | |||||
$head_label = pht('Label'); | |||||
$head_columns = pht('Columns'); | |||||
$orders = $query->getBuiltinOrders(); | $orders = $query->getBuiltinOrders(); | ||||
$table = array(); | $rows = array(); | ||||
$table[] = "| {$head_builtin} | {$head_label} | {$head_columns} |"; | |||||
$table[] = '|-----------------|---------------------|-----------------|'; | |||||
foreach ($orders as $key => $order) { | foreach ($orders as $key => $order) { | ||||
$name = $order['name']; | $rows[] = array( | ||||
$columns = implode(', ', $order['vector']); | $key, | ||||
$table[] = "| `{$key}` | {$name} | {$columns} |"; | $order['name'], | ||||
} | implode(', ', $order['vector']), | ||||
$table = implode("\n", $table); | ); | ||||
$out[] = $table; | } | ||||
$out[] = pht(<<<EOTEXT | $orders_table = id(new AphrontTableView($rows)) | ||||
You can choose a low-level column order instead. This is an advanced feature. | ->setHeaders( | ||||
array( | |||||
In your custom order: each column may only be specified once; each column may | pht('Key'), | ||||
be prefixed with "-" to invert the order; the last column must be unique; and | pht('Description'), | ||||
no column other than the last may be unique. | pht('Columns'), | ||||
)) | |||||
->setColumnClasses( | |||||
array( | |||||
'pri', | |||||
'', | |||||
'wide', | |||||
)); | |||||
$columns_info = pht(<<<EOTEXT | |||||
You can choose a low-level column order instead. To do this, provide a list | |||||
of columns instead of a single key. This is an advanced feature. | |||||
In a custom column order: | |||||
- each column may only be specified once; | |||||
- each column may be prefixed with `-` to invert the order; | |||||
- the last column must be a unique column, usually `id`; 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 | To use a low-level order, choose a sequence of columns and specify them like | ||||
this: | this: | ||||
```lang=json | ```lang=json, name="Using a Custom Order" | ||||
{ | { | ||||
... | ... | ||||
"order": ["color", "-name", "id"], | "order": ["color", "-name", "id"], | ||||
... | ... | ||||
} | } | ||||
``` | ``` | ||||
These low-level columns are available: | These low-level columns are available: | ||||
EOTEXT | EOTEXT | ||||
); | ); | ||||
$head_column = pht('Column Key'); | |||||
$head_unique = pht('Unique'); | |||||
$columns = $query->getOrderableColumns(); | $columns = $query->getOrderableColumns(); | ||||
$rows = array(); | |||||
$table = array(); | |||||
$table[] = "| {$head_column} | {$head_unique} |"; | |||||
$table[] = '|----------------|----------------|'; | |||||
foreach ($columns as $key => $column) { | foreach ($columns as $key => $column) { | ||||
$unique = idx($column, 'unique') | $rows[] = array( | ||||
? pht('Yes') | $key, | ||||
: pht('No'); | 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 | $columns_table = id(new AphrontTableView($rows)) | ||||
Fields | ->setHeaders( | ||||
------ | array( | ||||
pht('Key'), | |||||
The `data` field of the result contains a list of results. Each result has | pht('Unique'), | ||||
some metadata and a `fields` key, which contains the primary object fields. | )) | ||||
->setColumnClasses( | |||||
array( | |||||
'pri', | |||||
'wide', | |||||
)); | |||||
return id(new PHUIObjectBoxView()) | |||||
->setHeaderText(pht('Result Ordering')) | |||||
->setCollapsed(true) | |||||
->appendChild($this->buildRemarkup($orders_info)) | |||||
->appendChild($orders_table) | |||||
->appendChild($this->buildRemarkup($columns_info)) | |||||
->appendChild($columns_table); | |||||
} | |||||
private function buildFieldsBox( | |||||
PhabricatorApplicationSearchEngine $engine) { | |||||
$info = pht(<<<EOTEXT | |||||
Objects matching your query are returned as a list of dictionaries in the | |||||
`data` property of the results. Each dictionary has some metadata and a | |||||
`fields` key, which contains the information abou the object that most callers | |||||
will be interested in. | |||||
For example, the results may look something like this: | For example, the results may look something like this: | ||||
```lang=json | ```lang=json, name="Example Results" | ||||
{ | { | ||||
... | ... | ||||
"data": [ | "data": [ | ||||
{ | { | ||||
"id": 123, | "id": 123, | ||||
"phid": "PHID-WXYZ-1111", | "phid": "PHID-WXYZ-1111", | ||||
"fields": { | "fields": { | ||||
"name": "First Example Object", | "name": "First Example Object", | ||||
Show All 13 Lines | ```lang=json, name="Example Results" | ||||
... | ... | ||||
} | } | ||||
``` | ``` | ||||
This result structure is standardized across all search methods, but the | This result structure is standardized across all search methods, but the | ||||
available fields differ from application to application. | available fields differ from application to application. | ||||
These are the fields available on this object type: | These are the fields available on this object type: | ||||
EOTEXT | EOTEXT | ||||
); | ); | ||||
$head_key = pht('Key'); | |||||
$head_type = pht('Type'); | |||||
$head_description = pht('Description'); | |||||
$specs = $engine->getAllConduitFieldSpecifications(); | $specs = $engine->getAllConduitFieldSpecifications(); | ||||
$table = array(); | $rows = array(); | ||||
$table[] = "| {$head_key} | {$head_type} | {$head_description} |"; | |||||
$table[] = '|-------------|--------------|---------------------|'; | |||||
foreach ($specs as $key => $spec) { | foreach ($specs as $key => $spec) { | ||||
$type = idx($spec, 'type'); | $type = idx($spec, 'type'); | ||||
$description = idx($spec, 'description'); | $description = idx($spec, 'description'); | ||||
$table[] = "| `{$key}` | `{$type}` | {$description} |"; | $rows[] = array( | ||||
$key, | |||||
$type, | |||||
$description, | |||||
); | |||||
} | |||||
$table = id(new AphrontTableView($rows)) | |||||
->setHeaders( | |||||
array( | |||||
pht('Key'), | |||||
pht('Type'), | |||||
pht('Description'), | |||||
)) | |||||
->setColumnClasses( | |||||
array( | |||||
'pri', | |||||
'mono', | |||||
'wide', | |||||
)); | |||||
return id(new PHUIObjectBoxView()) | |||||
->setHeaderText(pht('Object Fields')) | |||||
->setCollapsed(true) | |||||
->appendChild($this->buildRemarkup($info)) | |||||
->appendChild($table); | |||||
} | } | ||||
$table = implode("\n", $table); | |||||
$out[] = $table; | |||||
$out[] = pht(<<<EOTEXT | private function buildPagingBox( | ||||
Paging and Limits | PhabricatorApplicationSearchEngine $engine) { | ||||
----------------- | |||||
$info = pht(<<<EOTEXT | |||||
Queries are limited to returning 100 results at a time. If you want fewer | 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. | 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 | If you want more results, you'll need to make additional queries to retrieve | ||||
more pages of results. | more pages of results. | ||||
The result structure contains a `cursor` key with information you'll need in | 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 | order to fetch the next page of results. After an initial query, it will | ||||
something like this: | usually look something like this: | ||||
```lang=json | ```lang=json, name="Example Cursor Result" | ||||
{ | { | ||||
... | ... | ||||
"cursor": { | "cursor": { | ||||
"limit": 100, | "limit": 100, | ||||
"after": "1234", | "after": "1234", | ||||
"before": null, | "before": null, | ||||
"order": null | "order": null | ||||
} | } | ||||
... | ... | ||||
} | } | ||||
``` | ``` | ||||
The `limit` and `order` fields are describing the effective limit and order the | 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 | 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 | `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. | 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 | 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 | 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. | 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 | If you do things correctly, you should get the second page of results, and | ||||
a cursor structure like this: | a cursor structure like this: | ||||
```lang=json | ```lang=json, name="Second Result Page" | ||||
{ | { | ||||
... | ... | ||||
"cursor": { | "cursor": { | ||||
"limit": 5, | "limit": 5, | ||||
"after": "4567", | "after": "4567", | ||||
"before": "7890", | "before": "7890", | ||||
"order": null | "order": null | ||||
} | } | ||||
... | ... | ||||
} | } | ||||
``` | ``` | ||||
You can now continue to the third page of results by passing the new `after` | 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 | 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 | 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 | might be useful if you are rendering a web UI for a user and want to provide | ||||
"Next Page" and "Previous Page" links. | "Next Page" and "Previous Page" links. | ||||
If `after` is `null`, there is no next page of results available. Likewise, | If `after` is `null`, there is no next page of results available. Likewise, | ||||
if `before` is `null`, there are no previous results available. | if `before` is `null`, there are no previous results available. | ||||
EOTEXT | EOTEXT | ||||
); | ); | ||||
$out = implode("\n\n", $out); | return id(new PHUIObjectBoxView()) | ||||
return $out; | ->setHeaderText(pht('Paging and Limits')) | ||||
->setCollapsed(true) | |||||
->appendChild($this->buildRemarkup($info)); | |||||
} | } | ||||
private function buildRemarkup($remarkup) { | |||||
$viewer = $this->getViewer(); | |||||
$view = new PHUIRemarkupView($viewer, $remarkup); | |||||
return id(new PHUIBoxView()) | |||||
->appendChild($view) | |||||
->addPadding(PHUI::PADDING_LARGE); | |||||
} | |||||
} | } |