1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-20 20:40:56 +01:00

Use ApplicationSearch to power primary search

Summary:
Ref T4365. Drive primary search through ApplicationSearch instead of through a bunch of custom nonsense. Notably, this allows you to save searches, notably.

The one thing this doesn't do -- which I'd like it to -- is carry your query text across searches. When you search for "quack", I want to overwrite the query in your default filter and give you those results, so you can turn the search into an "Open Tasks" search by default by reordering the queries. I'll probably do that next. It feels a little hacky but I want to try it out.

Test Plan: {F106932}

Reviewers: btrahan, chad

Reviewed By: btrahan

CC: aran, bigo

Maniphest Tasks: T4365

Differential Revision: https://secure.phabricator.com/D8123
This commit is contained in:
epriestley 2014-02-03 12:52:47 -08:00
parent a82f573102
commit b262ccdaff
9 changed files with 410 additions and 350 deletions

View file

@ -4692,7 +4692,11 @@ phutil_register_library_map(array(
'PhabricatorSearchAttachController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchBaseController' => 'PhabricatorController',
'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorSearchController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchController' =>
array(
0 => 'PhabricatorSearchBaseController',
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
),
'PhabricatorSearchDAO' => 'PhabricatorLiskDAO',
'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchDocument' => 'PhabricatorSearchDAO',

View file

@ -29,8 +29,7 @@ final class PhabricatorApplicationSearch extends PhabricatorApplication {
public function getRoutes() {
return array(
'/search/' => array(
'' => 'PhabricatorSearchController',
'(?P<key>[^/]+)/' => 'PhabricatorSearchController',
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorSearchController',
'attach/(?P<phid>[^/]+)/(?P<type>\w+)/(?:(?P<action>\w+)/)?'
=> 'PhabricatorSearchAttachController',
'select/(?P<type>\w+)/'

View file

@ -29,6 +29,7 @@ final class PhabricatorSearchConfigOptions
"your documents in some search engine which does not have ".
"default support.")),
$this->newOption('search.elastic.host', 'string', null)
->setLocked(true)
->setDescription(pht("Elastic Search host."))
->addExample('http://elastic.example.com:9200/', pht('Valid Setting')),
);

View file

@ -1,298 +1,98 @@
<?php
/**
* @group search
*/
final class PhabricatorSearchController
extends PhabricatorSearchBaseController {
extends PhabricatorSearchBaseController
implements PhabricatorApplicationSearchResultsControllerInterface {
private $key;
private $queryKey;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->key = idx($data, 'key');
$this->queryKey = idx($data, 'queryKey');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$viewer = $request->getUser();
if ($this->key) {
$query = id(new PhabricatorSavedQuery())->loadOneWhere(
'queryKey = %s',
$this->key);
if (!$query) {
return new Aphront404Response();
}
} else {
$query = id(new PhabricatorSavedQuery())
->setEngineClassName('PhabricatorSearchApplicationSearchEngine');
if ($request->isFormPost()) {
$query_str = $request->getStr('query');
$pref_jump = PhabricatorUserPreferences::PREFERENCE_SEARCHBAR_JUMP;
if ($request->getStr('jump') != 'no' &&
$user && $user->loadPreferences()->getPreference($pref_jump, 1)) {
$response = PhabricatorJumpNavHandler::getJumpResponse(
$user,
$query_str);
} else {
$response = null;
}
if ($request->getStr('jump') != 'no') {
$pref_jump = PhabricatorUserPreferences::PREFERENCE_SEARCHBAR_JUMP;
if ($viewer->loadPreferences($pref_jump, 1)) {
$response = PhabricatorJumpNavHandler::getJumpResponse(
$viewer,
$request->getStr('query'));
if ($response) {
return $response;
} else {
$query->setParameter('query', $query_str);
if ($request->getStr('scope')) {
switch ($request->getStr('scope')) {
case PhabricatorSearchScope::SCOPE_OPEN_REVISIONS:
$query->setParameter('open', 1);
$query->setParameter(
'type',
DifferentialPHIDTypeRevision::TYPECONST);
break;
case PhabricatorSearchScope::SCOPE_OPEN_TASKS:
$query->setParameter('open', 1);
$query->setParameter(
'type',
ManiphestPHIDTypeTask::TYPECONST);
break;
case PhabricatorSearchScope::SCOPE_WIKI:
$query->setParameter(
'type',
PhrictionPHIDTypeDocument::TYPECONST);
break;
case PhabricatorSearchScope::SCOPE_COMMITS:
$query->setParameter(
'type',
PhabricatorRepositoryPHIDTypeCommit::TYPECONST);
break;
default:
break;
}
} else {
if (strlen($request->getStr('type'))) {
$query->setParameter('type', $request->getStr('type'));
}
if ($request->getArr('author')) {
$query->setParameter('author', $request->getArr('author'));
}
if ($request->getArr('owner')) {
$query->setParameter('owner', $request->getArr('owner'));
}
if ($request->getArr('subscribers')) {
$query->setParameter('subscribers',
$request->getArr('subscribers'));
}
if ($request->getInt('open')) {
$query->setParameter('open', $request->getInt('open'));
}
if ($request->getArr('project')) {
$query->setParameter('project', $request->getArr('project'));
}
}
try {
$query->save();
} catch (AphrontQueryDuplicateKeyException $ex) {
// Someone has already executed this query.
}
return id(new AphrontRedirectResponse())
->setURI('/search/'.$query->getQueryKey().'/');
}
}
}
$options = array(
'' => 'All Documents',
) + PhabricatorSearchAbstractDocument::getSupportedTypes();
$controller = id(new PhabricatorApplicationSearchController($request))
->setQueryKey($this->queryKey)
->setSearchEngine(new PhabricatorSearchApplicationSearchEngine())
->setUseOffsetPaging(true)
->setNavigation($this->buildSideNavView());
$status_options = array(
0 => 'Open and Closed Documents',
1 => 'Open Documents',
);
$phids = array_merge(
$query->getParameter('author', array()),
$query->getParameter('owner', array()),
$query->getParameter('subscribers', array()),
$query->getParameter('project', array()));
$handles = $this->loadViewerHandles($phids);
$author_value = array_select_keys(
$handles,
$query->getParameter('author', array()));
$owner_value = array_select_keys(
$handles,
$query->getParameter('owner', array()));
$subscribers_value = array_select_keys(
$handles,
$query->getParameter('subscribers', array()));
$project_value = array_select_keys(
$handles,
$query->getParameter('project', array()));
$search_form = new AphrontFormView();
$search_form
->setUser($user)
->setAction('/search/')
->appendChild(
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => 'jump',
'value' => 'no',
)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Search')
->setName('query')
->setValue($query->getParameter('query')))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Document Type')
->setName('type')
->setOptions($options)
->setValue($query->getParameter('type')))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Document Status')
->setName('open')
->setOptions($status_options)
->setValue($query->getParameter('open')))
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('author')
->setLabel('Author')
->setDatasource('/typeahead/common/users/')
->setValue($author_value))
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('owner')
->setLabel('Owner')
->setDatasource('/typeahead/common/searchowner/')
->setValue($owner_value)
->setCaption(
'Tip: search for "Up For Grabs" to find unowned documents.'))
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('subscribers')
->setLabel('Subscribers')
->setDatasource('/typeahead/common/users/')
->setValue($subscribers_value))
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('project')
->setLabel('Project')
->setDatasource('/typeahead/common/projects/')
->setValue($project_value))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Search'));
$search_panel = new AphrontListFilterView();
$search_panel->appendChild($search_form);
require_celerity_resource('phabricator-search-results-css');
if ($query->getID()) {
$limit = 20;
$pager = new AphrontPagerView();
$pager->setURI($request->getRequestURI(), 'page');
$pager->setPageSize($limit);
$pager->setOffset($request->getInt('page'));
$query->setParameter('limit', $limit + 1);
$query->setParameter('offset', $pager->getOffset());
$engine = PhabricatorSearchEngineSelector::newSelector()->newEngine();
$results = $engine->executeSearch($query);
$results = $pager->sliceResults($results);
// If there are any objects which match the query by name, and we're
// not paging through the results, prefix the results with the named
// objects.
if (!$request->getInt('page')) {
$named = id(new PhabricatorObjectQuery())
->setViewer($user)
->withNames(array($query->getParameter('queyr')))
->execute();
if ($named) {
$results = array_merge(array_keys($named), $results);
}
}
if ($results) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($user)
->withPHIDs($results)
->execute();
$objects = id(new PhabricatorObjectQuery())
->setViewer($user)
->withPHIDs($results)
->execute();
$results = array();
foreach ($handles as $phid => $handle) {
$view = id(new PhabricatorSearchResultView())
->setHandle($handle)
->setQuery($query)
->setObject(idx($objects, $phid));
$results[] = $view->render();
}
$results = phutil_tag_div('phabricator-search-result-list', array(
phutil_implode_html("\n", $results),
phutil_tag_div('search-results-pager', $pager->render()),
));
} else {
$results = phutil_tag_div(
'phabricator-search-result-list',
phutil_tag(
'p',
array('class' => 'phabricator-search-no-results'),
pht('No search results.')));
}
$results = id(new PHUIBoxView())
->addMargin(PHUI::MARGIN_LARGE)
->addPadding(PHUI::PADDING_LARGE)
->setShadow(true)
->appendChild($results)
->addClass('phabricator-search-result-box');
} else {
$results = null;
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Search'));
return $this->buildApplicationPage(
array(
$crumbs,
$search_panel,
$results,
),
array(
'title' => pht('Search Results'),
'device' => true,
));
return $this->delegateToController($controller);
}
public function buildSideNavView($for_app = false) {
$viewer = $this->getRequest()->getUser();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new PhabricatorSearchApplicationSearchEngine())
->setViewer($viewer)
->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
return $nav;
}
public function renderResultsList(
array $results,
PhabricatorSavedQuery $query) {
$viewer = $this->getRequest()->getUser();
if ($results) {
$objects = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(mpull($results, 'getPHID'))
->execute();
$output = array();
foreach ($results as $phid => $handle) {
$view = id(new PhabricatorSearchResultView())
->setHandle($handle)
->setQuery($query)
->setObject(idx($objects, $phid));
$output[] = $view->render();
}
$results = phutil_tag_div(
'phabricator-search-result-list',
$output);
} else {
$results = phutil_tag_div(
'phabricator-search-result-list',
phutil_tag(
'p',
array('class' => 'phabricator-search-no-results'),
pht('No search results.')));
}
return id(new PHUIBoxView())
->addMargin(PHUI::MARGIN_LARGE)
->addPadding(PHUI::PADDING_LARGE)
->setShadow(true)
->appendChild($results)
->addClass('phabricator-search-result-box');
}
}

View file

@ -95,7 +95,7 @@ final class PhabricatorSearchEngineElastic extends PhabricatorSearchEngine {
$spec = array();
$filter = array();
if ($query->getParameter('query') != '') {
if (strlen($query->getParameter('query'))) {
$spec[] = array(
'field' => array(
'field.corpus' => $query->getParameter('query'),
@ -114,17 +114,41 @@ final class PhabricatorSearchEngineElastic extends PhabricatorSearchEngine {
);
}
$rel_mapping = array(
'author' => PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR,
'open' => PhabricatorSearchRelationship::RELATIONSHIP_OPEN,
'owner' => PhabricatorSearchRelationship::RELATIONSHIP_OWNER,
'subscribers' => PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER,
'project' => PhabricatorSearchRelationship::RELATIONSHIP_PROJECT,
'repository' => PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY,
$relationship_map = array(
PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR =>
$query->getParameter('authorPHIDs', array()),
PhabricatorSearchRelationship::RELATIONSHIP_OWNER =>
$query->getParameter('ownerPHIDs', array()),
PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER =>
$query->getParameter('subscriberPHIDs', array()),
PhabricatorSearchRelationship::RELATIONSHIP_PROJECT =>
$query->getParameter('projectPHIDs', array()),
PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY =>
$query->getParameter('repositoryPHIDs', array()),
);
foreach ($rel_mapping as $name => $field) {
$param = $query->getParameter($name);
if (is_array($param)) {
$statuses = $query->getParameter('statuses', array());
$statuses = array_fuse($statuses);
$rel_open = PhabricatorSearchRelationship::RELATIONSHIP_OPEN;
$rel_closed = PhabricatorSearchRelationship::RELATIONSHIP_CLOSED;
$rel_unowned = PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED;
$include_open = !empty($statuses[$rel_open]);
$include_closed = !empty($statuses[$rel_closed]);
if ($include_open && !$include_closed) {
$relationship_map[$rel_open] = true;
} else if (!$include_open && $include_closed) {
$relationship_map[$rel_closed] = true;
}
if ($query->getParameter('withUnowned')) {
$relationship_map[$rel_unowned] = true;
}
foreach ($relationship_map as $field => $param) {
if (is_array($param) && $param) {
$should = array();
foreach ($param as $val) {
$should[] = array(
@ -177,24 +201,24 @@ final class PhabricatorSearchEngineElastic extends PhabricatorSearchEngine {
}
public function executeSearch(PhabricatorSavedQuery $query) {
$type = $query->getParameter('type');
if ($type) {
$uri = "/phabricator/{$type}/_search";
} else {
// Don't use '/phabricator/_search' for the case that there is something
// else in the index (for example if 'phabricator' is only an alias to
// some bigger index).
$types = PhabricatorSearchAbstractDocument::getSupportedTypes();
$uri = '/phabricator/' . implode(',', array_keys($types)) . '/_search';
$types = $query->getParameter('types');
if (!$types) {
$types = array_keys(
PhabricatorSearchApplicationSearchEngine::getIndexableDocumentTypes());
}
// Don't use '/phabricator/_search' for the case that there is something
// else in the index (for example if 'phabricator' is only an alias to
// some bigger index).
$uri = '/phabricator/'.implode(',', $types).'/_search';
try {
$response = $this->executeRequest($uri, $this->buildSpec($query));
} catch (HTTPFutureResponseStatusHTTP $ex) {
// elasticsearch probably uses Lucene query syntax:
// http://lucene.apache.org/core/3_6_1/queryparsersyntax.html
// Try literal search if operator search fails.
if (!$query->getParameter('query')) {
if (!strlen($query->getParameter('query'))) {
throw $ex;
}
$query = clone $query;

View file

@ -179,7 +179,7 @@ final class PhabricatorSearchEngineMySQL extends PhabricatorSearchEngine {
$q);
$field = $query->getParameter('field');
if ($field/* && $field != AdjutantQuery::FIELD_ALL*/) {
if ($field) {
$where[] = qsprintf(
$conn_r,
'field.field = %s',
@ -192,49 +192,74 @@ final class PhabricatorSearchEngineMySQL extends PhabricatorSearchEngine {
$where[] = qsprintf($conn_r, 'document.phid != %s', $exclude);
}
if ($query->getParameter('type')) {
$types = $query->getParameter('types');
if ($types) {
if (strlen($q)) {
// TODO: verify that this column actually does something useful in query
// plans once we have nontrivial amounts of data.
$where[] = qsprintf(
$conn_r,
'field.phidType = %s',
$query->getParameter('type'));
'field.phidType IN (%Ls)',
$types);
}
$where[] = qsprintf(
$conn_r,
'document.documentType = %s',
$query->getParameter('type'));
'document.documentType IN (%Ls)',
$types);
}
$join[] = $this->joinRelationship(
$conn_r,
$query,
'author',
'authorPHIDs',
PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR);
$join[] = $this->joinRelationship(
$conn_r,
$query,
'open',
PhabricatorSearchRelationship::RELATIONSHIP_OPEN);
$statuses = $query->getParameter('statuses', array());
$statuses = array_fuse($statuses);
$open_rel = PhabricatorSearchRelationship::RELATIONSHIP_OPEN;
$closed_rel = PhabricatorSearchRelationship::RELATIONSHIP_CLOSED;
$include_open = !empty($statuses[$open_rel]);
$include_closed = !empty($statuses[$closed_rel]);
if ($include_open && !$include_closed) {
$join[] = $this->joinRelationship(
$conn_r,
$query,
'statuses',
$open_rel,
true);
} else if ($include_closed && !$include_open) {
$join[] = $this->joinRelationship(
$conn_r,
$query,
'statuses',
$closed_rel,
true);
}
if ($query->getParameter('withUnowned')) {
$join[] = $this->joinRelationship(
$conn_r,
$query,
'withUnowned',
PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED,
true);
} else {
$join[] = $this->joinRelationship(
$conn_r,
$query,
'ownerPHIDs',
PhabricatorSearchRelationship::RELATIONSHIP_OWNER);
}
$join[] = $this->joinRelationship(
$conn_r,
$query,
'owner',
PhabricatorSearchRelationship::RELATIONSHIP_OWNER);
$join[] = $this->joinRelationship(
$conn_r,
$query,
'subscribers',
'subscriberPHIDs',
PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER);
$join[] = $this->joinRelationship(
$conn_r,
$query,
'project',
'projectPHIDs',
PhabricatorSearchRelationship::RELATIONSHIP_PROJECT);
$join[] = $this->joinRelationship(
@ -283,19 +308,8 @@ final class PhabricatorSearchEngineMySQL extends PhabricatorSearchEngine {
AphrontDatabaseConnection $conn,
PhabricatorSavedQuery $query,
$field,
$type) {
$phids = $query->getParameter($field, array());
if (!$phids) {
return null;
}
$is_existence = false;
switch ($type) {
case PhabricatorSearchRelationship::RELATIONSHIP_OPEN:
$is_existence = true;
break;
}
$type,
$is_existence = false) {
$sql = qsprintf(
$conn,
@ -307,6 +321,10 @@ final class PhabricatorSearchEngineMySQL extends PhabricatorSearchEngine {
$type);
if (!$is_existence) {
$phids = $query->getParameter($field, array());
if (!$phids) {
return null;
}
$sql .= qsprintf(
$conn,
' AND %C.relatedPHID in (%Ls)',

View file

@ -1,8 +1,5 @@
<?php
/**
* @group search
*/
final class PhabricatorSearchAbstractDocument {
private $phid;
@ -13,17 +10,6 @@ final class PhabricatorSearchAbstractDocument {
private $fields = array();
private $relationships = array();
public static function getSupportedTypes() {
return array(
DifferentialPHIDTypeRevision::TYPECONST => 'Differential Revisions',
PhabricatorRepositoryPHIDTypeCommit::TYPECONST => 'Repository Commits',
ManiphestPHIDTypeTask::TYPECONST => 'Maniphest Tasks',
PhrictionPHIDTypeDocument::TYPECONST => 'Phriction Documents',
PhabricatorPeoplePHIDTypeUser::TYPECONST => 'Phabricator Users',
PonderPHIDTypeQuestion::TYPECONST => 'Ponder Questions',
);
}
public function setPHID($phid) {
$this->phid = $phid;
return $this;

View file

@ -6,19 +6,155 @@ final class PhabricatorSearchApplicationSearchEngine
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
$saved->setParameter('query', $request->getStr('query'));
$saved->setParameter(
'statuses',
$this->readListFromRequest($request, 'statuses'));
$saved->setParameter(
'types',
$this->readListFromRequest($request, 'types'));
$saved->setParameter(
'authorPHIDs',
$this->readUsersFromRequest($request, 'authorPHIDs'));
$saved->setParameter(
'ownerPHIDs',
$this->readUsersFromRequest($request, 'ownerPHIDs'));
$saved->setParameter(
'withUnowned',
$this->readBoolFromRequest($request, 'withUnowned'));
$saved->setParameter(
'subscriberPHIDs',
$this->readPHIDsFromRequest($request, 'subscriberPHIDs'));
$saved->setParameter(
'projectPHIDs',
$this->readPHIDsFromRequest($request, 'projectPHIDs'));
return $saved;
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new PhabricatorSearchDocumentQuery());
$query = id(new PhabricatorSearchDocumentQuery())
->withSavedQuery($saved);
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved_query) {
return;
PhabricatorSavedQuery $saved) {
$options = array();
$author_value = null;
$owner_value = null;
$subscribers_value = null;
$project_value = null;
$author_phids = $saved->getParameter('authorPHIDs', array());
$owner_phids = $saved->getParameter('ownerPHIDs', array());
$subscriber_phids = $saved->getParameter('subscriberPHIDs', array());
$project_phids = $saved->getParameter('projectPHIDs', array());
$all_phids = array_merge(
$author_phids,
$owner_phids,
$subscriber_phids,
$project_phids);
$all_handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($all_phids)
->execute();
$author_handles = array_select_keys($all_handles, $author_phids);
$owner_handles = array_select_keys($all_handles, $owner_phids);
$subscriber_handles = array_select_keys($all_handles, $subscriber_phids);
$project_handles = array_select_keys($all_handles, $project_phids);
$with_unowned = $saved->getParameter('withUnowned', array());
$status_values = $saved->getParameter('statuses', array());
$status_values = array_fuse($status_values);
$statuses = array(
PhabricatorSearchRelationship::RELATIONSHIP_OPEN => pht('Open'),
PhabricatorSearchRelationship::RELATIONSHIP_CLOSED => pht('Closed'),
);
$status_control = id(new AphrontFormCheckboxControl())
->setLabel(pht('Document Status'));
foreach ($statuses as $status => $name) {
$status_control->addCheckbox(
'statuses[]',
$status,
$name,
isset($status_values[$status]));
}
$type_values = $saved->getParameter('types', array());
$type_values = array_fuse($type_values);
$types = self::getIndexableDocumentTypes();
$types_control = id(new AphrontFormCheckboxControl())
->setLabel(pht('Document Types'));
foreach ($types as $type => $name) {
$types_control->addCheckbox(
'types[]',
$type,
$name,
isset($type_values[$type]));
}
$form
->appendChild(
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => 'jump',
'value' => 'no',
)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Query')
->setName('query')
->setValue($saved->getParameter('query')))
->appendChild($status_control)
->appendChild($types_control)
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('authorPHIDs')
->setLabel('Authors')
->setDatasource('/typeahead/common/users/')
->setValue($author_handles))
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('ownerPHIDs')
->setLabel('Owners')
->setDatasource('/typeahead/common/searchowner/')
->setValue($owner_handles))
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'withUnowned',
1,
pht('Show only unowned documents.'),
$with_unowned))
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('subscriberPHIDs')
->setLabel('Subscribers')
->setDatasource('/typeahead/common/users/')
->setValue($subscriber_handles))
->appendChild(
id(new AphrontFormTokenizerControl())
->setName('projectPHIDs')
->setLabel('In Any Project')
->setDatasource('/typeahead/common/projects/')
->setValue($project_handles));
}
protected function getURI($path) {
@ -28,6 +164,8 @@ final class PhabricatorSearchApplicationSearchEngine
public function getBuiltinQueryNames() {
$names = array(
'all' => pht('All Documents'),
'open' => pht('Open Documents'),
'open-tasks' => pht('Open Tasks'),
);
return $names;
@ -41,9 +179,45 @@ final class PhabricatorSearchApplicationSearchEngine
switch ($query_key) {
case 'all':
return $query;
case 'open':
return $query->setParameter('statuses', array('open'));
case 'open-tasks':
return $query
->setParameter('statuses', array('open'))
->setParameter('types', array(ManiphestPHIDTypeTask::TYPECONST));
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
public static function getIndexableDocumentTypes() {
// TODO: This is inelegant and not very efficient, but gets us reasonable
// results. It would be nice to do this more elegantly.
// TODO: We should hide types associated with applications the user can
// not access. There's no reasonable way to do this right now.
$indexers = id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorSearchDocumentIndexer')
->loadObjects();
$types = PhabricatorPHIDType::getAllTypes();
$results = array();
foreach ($types as $type) {
$typeconst = $type->getTypeConstant();
foreach ($indexers as $indexer) {
$fake_phid = 'PHID-'.$typeconst.'-fake';
if ($indexer->shouldIndexDocumentByPHID($fake_phid)) {
$results[$typeconst] = $type->getTypeName();
}
}
}
asort($results);
return $results;
}
}

View file

@ -3,12 +3,66 @@
final class PhabricatorSearchDocumentQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $savedQuery;
public function withSavedQuery(PhabricatorSavedQuery $query) {
$this->savedQuery = $query;
return $this;
}
protected function loadPage() {
return array();
$phids = $this->loadDocumentPHIDsWithoutPolicyChecks();
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->getViewer())
->withPHIDs($phids)
->execute();
// Retain engine order.
$handles = array_select_keys($handles, $phids);
return $handles;
}
protected function willFilterPage(array $handles) {
foreach ($handles as $key => $handle) {
if (!$handle->isComplete()) {
unset($handles[$key]);
continue;
}
if ($handle->getPolicyFiltered()) {
unset($handles[$key]);
continue;
}
}
return $handles;
}
public function loadDocumentPHIDsWithoutPolicyChecks() {
$query = id(clone($this->savedQuery))
->setParameter('offset', $this->getOffset())
->setParameter('limit', $this->getRawResultLimit());
$engine = PhabricatorSearchEngineSelector::newSelector()->newEngine();
return $engine->executeSearch($query);
}
public function getQueryApplicationClass() {
return 'PhabricatorApplicationSearch';
}
protected function getPagingValue($result) {
throw new Exception(
pht(
'This query does not support cursor paging; it must be offset '.
'paged.'));
}
protected function nextPage(array $page) {
$this->setOffset($this->getOffset() + count($page));
return $this;
}
}