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:
parent
a82f573102
commit
b262ccdaff
9 changed files with 410 additions and 350 deletions
|
@ -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',
|
||||
|
|
|
@ -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+)/'
|
||||
|
|
|
@ -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')),
|
||||
);
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue