2015-12-11 17:45:56 +01:00
|
|
|
<?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() {
|
2015-12-13 17:45:01 +01:00
|
|
|
return pht(
|
|
|
|
'This is a standard **ApplicationSearch** method which will let you '.
|
|
|
|
'list, query, or search for objects.');
|
|
|
|
}
|
|
|
|
|
|
|
|
final public function getMethodDocumentation() {
|
2015-12-13 11:45:08 +01:00
|
|
|
$viewer = $this->getViewer();
|
2015-12-11 17:45:56 +01:00
|
|
|
|
|
|
|
$engine = $this->newSearchEngine()
|
|
|
|
->setViewer($viewer);
|
|
|
|
|
|
|
|
$query = $engine->newQuery();
|
|
|
|
|
|
|
|
$out = array();
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$out[] = $this->buildQueriesBox($engine);
|
|
|
|
$out[] = $this->buildConstraintsBox($engine);
|
|
|
|
$out[] = $this->buildOrderBox($engine, $query);
|
|
|
|
$out[] = $this->buildFieldsBox($engine);
|
|
|
|
$out[] = $this->buildPagingBox($engine);
|
2015-12-11 17:45:56 +01:00
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
return $out;
|
|
|
|
}
|
2015-12-11 17:45:56 +01:00
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
private function buildQueriesBox(
|
|
|
|
PhabricatorApplicationSearchEngine $engine) {
|
|
|
|
$viewer = $this->getViewer();
|
2015-12-11 17:45:56 +01:00
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$info = pht(<<<EOTEXT
|
|
|
|
You can choose a builtin or saved query as a starting point for filtering
|
|
|
|
results by selecting it with `queryKey`. If you don't specify a `queryKey`,
|
|
|
|
the query will start with no constraints.
|
2015-12-11 17:45:56 +01:00
|
|
|
|
|
|
|
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:
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
```lang=json, name="Selecting a Builtin Query"
|
2015-12-11 17:45:56 +01:00
|
|
|
{
|
|
|
|
...
|
|
|
|
"queryKey": "active",
|
|
|
|
...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
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.
|
|
|
|
|
2015-12-13 11:45:08 +01:00
|
|
|
You can use these keys to select builtin queries and your configured saved
|
|
|
|
queries:
|
2015-12-11 17:45:56 +01:00
|
|
|
EOTEXT
|
|
|
|
);
|
|
|
|
|
|
|
|
$named_queries = $engine->loadAllNamedQueries();
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$rows = array();
|
2015-12-11 17:45:56 +01:00
|
|
|
foreach ($named_queries as $named_query) {
|
|
|
|
$builtin = $named_query->getIsBuiltin()
|
|
|
|
? pht('Builtin')
|
|
|
|
: pht('Custom');
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$rows[] = array(
|
|
|
|
$named_query->getQueryKey(),
|
|
|
|
$named_query->getQueryName(),
|
|
|
|
$builtin,
|
|
|
|
);
|
2015-12-11 17:45:56 +01:00
|
|
|
}
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$table = id(new AphrontTableView($rows))
|
|
|
|
->setHeaders(
|
|
|
|
array(
|
|
|
|
pht('Query Key'),
|
|
|
|
pht('Name'),
|
|
|
|
pht('Builtin'),
|
|
|
|
))
|
|
|
|
->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).
|
2015-12-11 17:45:56 +01:00
|
|
|
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
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.
|
2015-12-11 17:45:56 +01:00
|
|
|
|
|
|
|
Specify constraints like this:
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
```lang=json, name="Example Custom Constraints"
|
2015-12-11 17:45:56 +01:00
|
|
|
{
|
|
|
|
...
|
|
|
|
"constraints": {
|
2015-12-13 17:45:01 +01:00
|
|
|
"authors": ["PHID-USER-1111", "PHID-USER-2222"],
|
|
|
|
"statuses": ["open", "closed"],
|
|
|
|
...
|
2015-12-11 17:45:56 +01:00
|
|
|
},
|
|
|
|
...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
This API endpoint supports these constraints:
|
|
|
|
EOTEXT
|
|
|
|
);
|
|
|
|
|
|
|
|
$fields = $engine->getSearchFieldsForConduit();
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$rows = array();
|
2015-12-11 17:45:56 +01:00
|
|
|
foreach ($fields as $field) {
|
2015-12-13 12:53:15 +01:00
|
|
|
$key = $field->getConduitKey();
|
2015-12-11 17:45:56 +01:00
|
|
|
$label = $field->getLabel();
|
|
|
|
|
2015-12-13 12:53:15 +01:00
|
|
|
$type_object = $field->getConduitParameterType();
|
|
|
|
if ($type_object) {
|
2015-12-13 17:45:01 +01:00
|
|
|
$type = $type_object->getTypeName();
|
2015-12-13 15:52:37 +01:00
|
|
|
$description = $field->getDescription();
|
2015-12-13 12:53:15 +01:00
|
|
|
} else {
|
2015-12-13 17:45:01 +01:00
|
|
|
$type = null;
|
|
|
|
$description = phutil_tag('em', array(), pht('Not supported.'));
|
2015-12-13 12:53:15 +01:00
|
|
|
}
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$rows[] = array(
|
|
|
|
$key,
|
|
|
|
$label,
|
|
|
|
$type,
|
|
|
|
$description,
|
|
|
|
);
|
2015-12-11 17:45:56 +01:00
|
|
|
}
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$table = id(new AphrontTableView($rows))
|
|
|
|
->setHeaders(
|
|
|
|
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) {
|
2015-12-11 17:45:56 +01:00
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$orders_info = pht(<<<EOTEXT
|
|
|
|
Use `order` to choose an ordering for the results.
|
2015-12-11 17:45:56 +01:00
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
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.
|
2015-12-11 17:45:56 +01:00
|
|
|
|
|
|
|
To use a high-level order, choose a builtin order from the table below
|
|
|
|
and specify it like this:
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
```lang=json, name="Choosing a Result Order"
|
2015-12-11 17:45:56 +01:00
|
|
|
{
|
|
|
|
...
|
|
|
|
"order": "newest",
|
|
|
|
...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
These builtin orders are available:
|
|
|
|
EOTEXT
|
|
|
|
);
|
|
|
|
|
|
|
|
$orders = $query->getBuiltinOrders();
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$rows = array();
|
2015-12-11 17:45:56 +01:00
|
|
|
foreach ($orders as $key => $order) {
|
2015-12-13 17:45:01 +01:00
|
|
|
$rows[] = array(
|
|
|
|
$key,
|
|
|
|
$order['name'],
|
|
|
|
implode(', ', $order['vector']),
|
|
|
|
);
|
2015-12-11 17:45:56 +01:00
|
|
|
}
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$orders_table = id(new AphrontTableView($rows))
|
|
|
|
->setHeaders(
|
|
|
|
array(
|
|
|
|
pht('Key'),
|
|
|
|
pht('Description'),
|
|
|
|
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.
|
2015-12-11 17:45:56 +01:00
|
|
|
|
|
|
|
To use a low-level order, choose a sequence of columns and specify them like
|
|
|
|
this:
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
```lang=json, name="Using a Custom Order"
|
2015-12-11 17:45:56 +01:00
|
|
|
{
|
|
|
|
...
|
|
|
|
"order": ["color", "-name", "id"],
|
|
|
|
...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
These low-level columns are available:
|
|
|
|
EOTEXT
|
|
|
|
);
|
|
|
|
|
|
|
|
$columns = $query->getOrderableColumns();
|
2015-12-13 17:45:01 +01:00
|
|
|
$rows = array();
|
2015-12-11 17:45:56 +01:00
|
|
|
foreach ($columns as $key => $column) {
|
2015-12-13 17:45:01 +01:00
|
|
|
$rows[] = array(
|
|
|
|
$key,
|
|
|
|
idx($column, 'unique') ? pht('Yes') : pht('No'),
|
|
|
|
);
|
2015-12-11 17:45:56 +01:00
|
|
|
}
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$columns_table = id(new AphrontTableView($rows))
|
|
|
|
->setHeaders(
|
|
|
|
array(
|
|
|
|
pht('Key'),
|
|
|
|
pht('Unique'),
|
|
|
|
))
|
|
|
|
->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);
|
|
|
|
}
|
2015-12-11 17:45:56 +01:00
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
private function buildFieldsBox(
|
|
|
|
PhabricatorApplicationSearchEngine $engine) {
|
2015-12-11 17:45:56 +01:00
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$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.
|
2015-12-11 17:45:56 +01:00
|
|
|
|
|
|
|
For example, the results may look something like this:
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
```lang=json, name="Example Results"
|
2015-12-11 17:45:56 +01:00
|
|
|
{
|
|
|
|
...
|
|
|
|
"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();
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$rows = array();
|
2015-12-11 17:45:56 +01:00
|
|
|
foreach ($specs as $key => $spec) {
|
|
|
|
$type = idx($spec, 'type');
|
|
|
|
$description = idx($spec, 'description');
|
2015-12-13 17:45:01 +01:00
|
|
|
$rows[] = array(
|
|
|
|
$key,
|
|
|
|
$type,
|
|
|
|
$description,
|
|
|
|
);
|
2015-12-11 17:45:56 +01:00
|
|
|
}
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$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);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function buildPagingBox(
|
|
|
|
PhabricatorApplicationSearchEngine $engine) {
|
2015-12-11 17:45:56 +01:00
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
$info = pht(<<<EOTEXT
|
2015-12-11 17:45:56 +01:00
|
|
|
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
|
2015-12-13 17:45:01 +01:00
|
|
|
order to fetch the next page of results. After an initial query, it will
|
|
|
|
usually look something like this:
|
2015-12-11 17:45:56 +01:00
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
```lang=json, name="Example Cursor Result"
|
2015-12-11 17:45:56 +01:00
|
|
|
{
|
|
|
|
...
|
|
|
|
"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:
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
```lang=json, name="Second Result Page"
|
2015-12-11 17:45:56 +01:00
|
|
|
{
|
|
|
|
...
|
|
|
|
"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
|
|
|
|
);
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
return id(new PHUIObjectBoxView())
|
|
|
|
->setHeaderText(pht('Paging and Limits'))
|
|
|
|
->setCollapsed(true)
|
|
|
|
->appendChild($this->buildRemarkup($info));
|
2015-12-11 17:45:56 +01:00
|
|
|
}
|
|
|
|
|
2015-12-13 17:45:01 +01:00
|
|
|
private function buildRemarkup($remarkup) {
|
|
|
|
$viewer = $this->getViewer();
|
|
|
|
|
|
|
|
$view = new PHUIRemarkupView($viewer, $remarkup);
|
|
|
|
|
|
|
|
return id(new PHUIBoxView())
|
|
|
|
->appendChild($view)
|
|
|
|
->addPadding(PHUI::PADDING_LARGE);
|
|
|
|
}
|
2015-12-11 17:45:56 +01:00
|
|
|
}
|