@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+396 -336
+5 -1
src/__phutil_library_map__.php
··· 4692 4692 'PhabricatorSearchAttachController' => 'PhabricatorSearchBaseController', 4693 4693 'PhabricatorSearchBaseController' => 'PhabricatorController', 4694 4694 'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions', 4695 - 'PhabricatorSearchController' => 'PhabricatorSearchBaseController', 4695 + 'PhabricatorSearchController' => 4696 + array( 4697 + 0 => 'PhabricatorSearchBaseController', 4698 + 1 => 'PhabricatorApplicationSearchResultsControllerInterface', 4699 + ), 4696 4700 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 4697 4701 'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController', 4698 4702 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO',
+1 -2
src/applications/search/application/PhabricatorApplicationSearch.php
··· 29 29 public function getRoutes() { 30 30 return array( 31 31 '/search/' => array( 32 - '' => 'PhabricatorSearchController', 33 - '(?P<key>[^/]+)/' => 'PhabricatorSearchController', 32 + '(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorSearchController', 34 33 'attach/(?P<phid>[^/]+)/(?P<type>\w+)/(?:(?P<action>\w+)/)?' 35 34 => 'PhabricatorSearchAttachController', 36 35 'select/(?P<type>\w+)/'
+1
src/applications/search/config/PhabricatorSearchConfigOptions.php
··· 29 29 "your documents in some search engine which does not have ". 30 30 "default support.")), 31 31 $this->newOption('search.elastic.host', 'string', null) 32 + ->setLocked(true) 32 33 ->setDescription(pht("Elastic Search host.")) 33 34 ->addExample('http://elastic.example.com:9200/', pht('Valid Setting')), 34 35 );
+59 -259
src/applications/search/controller/PhabricatorSearchController.php
··· 1 1 <?php 2 2 3 - /** 4 - * @group search 5 - */ 6 3 final class PhabricatorSearchController 7 - extends PhabricatorSearchBaseController { 4 + extends PhabricatorSearchBaseController 5 + implements PhabricatorApplicationSearchResultsControllerInterface { 8 6 9 - private $key; 7 + private $queryKey; 10 8 11 9 public function shouldAllowPublic() { 12 10 return true; 13 11 } 14 12 15 13 public function willProcessRequest(array $data) { 16 - $this->key = idx($data, 'key'); 14 + $this->queryKey = idx($data, 'queryKey'); 17 15 } 18 16 19 17 public function processRequest() { 20 18 $request = $this->getRequest(); 21 - $user = $request->getUser(); 22 - 23 - if ($this->key) { 24 - $query = id(new PhabricatorSavedQuery())->loadOneWhere( 25 - 'queryKey = %s', 26 - $this->key); 27 - if (!$query) { 28 - return new Aphront404Response(); 29 - } 30 - } else { 31 - $query = id(new PhabricatorSavedQuery()) 32 - ->setEngineClassName('PhabricatorSearchApplicationSearchEngine'); 33 - 34 - if ($request->isFormPost()) { 35 - $query_str = $request->getStr('query'); 19 + $viewer = $request->getUser(); 36 20 37 - $pref_jump = PhabricatorUserPreferences::PREFERENCE_SEARCHBAR_JUMP; 38 - if ($request->getStr('jump') != 'no' && 39 - $user && $user->loadPreferences()->getPreference($pref_jump, 1)) { 40 - $response = PhabricatorJumpNavHandler::getJumpResponse( 41 - $user, 42 - $query_str); 43 - } else { 44 - $response = null; 45 - } 21 + if ($request->getStr('jump') != 'no') { 22 + $pref_jump = PhabricatorUserPreferences::PREFERENCE_SEARCHBAR_JUMP; 23 + if ($viewer->loadPreferences($pref_jump, 1)) { 24 + $response = PhabricatorJumpNavHandler::getJumpResponse( 25 + $viewer, 26 + $request->getStr('query')); 46 27 if ($response) { 47 28 return $response; 48 - } else { 49 - $query->setParameter('query', $query_str); 50 - 51 - if ($request->getStr('scope')) { 52 - switch ($request->getStr('scope')) { 53 - case PhabricatorSearchScope::SCOPE_OPEN_REVISIONS: 54 - $query->setParameter('open', 1); 55 - $query->setParameter( 56 - 'type', 57 - DifferentialPHIDTypeRevision::TYPECONST); 58 - break; 59 - case PhabricatorSearchScope::SCOPE_OPEN_TASKS: 60 - $query->setParameter('open', 1); 61 - $query->setParameter( 62 - 'type', 63 - ManiphestPHIDTypeTask::TYPECONST); 64 - break; 65 - case PhabricatorSearchScope::SCOPE_WIKI: 66 - $query->setParameter( 67 - 'type', 68 - PhrictionPHIDTypeDocument::TYPECONST); 69 - break; 70 - case PhabricatorSearchScope::SCOPE_COMMITS: 71 - $query->setParameter( 72 - 'type', 73 - PhabricatorRepositoryPHIDTypeCommit::TYPECONST); 74 - break; 75 - default: 76 - break; 77 - } 78 - } else { 79 - if (strlen($request->getStr('type'))) { 80 - $query->setParameter('type', $request->getStr('type')); 81 - } 82 - 83 - if ($request->getArr('author')) { 84 - $query->setParameter('author', $request->getArr('author')); 85 - } 86 - 87 - if ($request->getArr('owner')) { 88 - $query->setParameter('owner', $request->getArr('owner')); 89 - } 90 - 91 - if ($request->getArr('subscribers')) { 92 - $query->setParameter('subscribers', 93 - $request->getArr('subscribers')); 94 - } 95 - 96 - if ($request->getInt('open')) { 97 - $query->setParameter('open', $request->getInt('open')); 98 - } 99 - 100 - if ($request->getArr('project')) { 101 - $query->setParameter('project', $request->getArr('project')); 102 - } 103 - } 104 - 105 - try { 106 - $query->save(); 107 - } catch (AphrontQueryDuplicateKeyException $ex) { 108 - // Someone has already executed this query. 109 - } 110 - return id(new AphrontRedirectResponse()) 111 - ->setURI('/search/'.$query->getQueryKey().'/'); 112 29 } 113 30 } 114 31 } 115 32 116 - $options = array( 117 - '' => 'All Documents', 118 - ) + PhabricatorSearchAbstractDocument::getSupportedTypes(); 33 + $controller = id(new PhabricatorApplicationSearchController($request)) 34 + ->setQueryKey($this->queryKey) 35 + ->setSearchEngine(new PhabricatorSearchApplicationSearchEngine()) 36 + ->setUseOffsetPaging(true) 37 + ->setNavigation($this->buildSideNavView()); 119 38 120 - $status_options = array( 121 - 0 => 'Open and Closed Documents', 122 - 1 => 'Open Documents', 123 - ); 39 + return $this->delegateToController($controller); 40 + } 124 41 125 - $phids = array_merge( 126 - $query->getParameter('author', array()), 127 - $query->getParameter('owner', array()), 128 - $query->getParameter('subscribers', array()), 129 - $query->getParameter('project', array())); 130 - 131 - $handles = $this->loadViewerHandles($phids); 132 - 133 - $author_value = array_select_keys( 134 - $handles, 135 - $query->getParameter('author', array())); 136 - 137 - $owner_value = array_select_keys( 138 - $handles, 139 - $query->getParameter('owner', array())); 140 - 141 - $subscribers_value = array_select_keys( 142 - $handles, 143 - $query->getParameter('subscribers', array())); 144 - 145 - $project_value = array_select_keys( 146 - $handles, 147 - $query->getParameter('project', array())); 148 - 149 - $search_form = new AphrontFormView(); 150 - $search_form 151 - ->setUser($user) 152 - ->setAction('/search/') 153 - ->appendChild( 154 - phutil_tag( 155 - 'input', 156 - array( 157 - 'type' => 'hidden', 158 - 'name' => 'jump', 159 - 'value' => 'no', 160 - ))) 161 - ->appendChild( 162 - id(new AphrontFormTextControl()) 163 - ->setLabel('Search') 164 - ->setName('query') 165 - ->setValue($query->getParameter('query'))) 166 - ->appendChild( 167 - id(new AphrontFormSelectControl()) 168 - ->setLabel('Document Type') 169 - ->setName('type') 170 - ->setOptions($options) 171 - ->setValue($query->getParameter('type'))) 172 - ->appendChild( 173 - id(new AphrontFormSelectControl()) 174 - ->setLabel('Document Status') 175 - ->setName('open') 176 - ->setOptions($status_options) 177 - ->setValue($query->getParameter('open'))) 178 - ->appendChild( 179 - id(new AphrontFormTokenizerControl()) 180 - ->setName('author') 181 - ->setLabel('Author') 182 - ->setDatasource('/typeahead/common/users/') 183 - ->setValue($author_value)) 184 - ->appendChild( 185 - id(new AphrontFormTokenizerControl()) 186 - ->setName('owner') 187 - ->setLabel('Owner') 188 - ->setDatasource('/typeahead/common/searchowner/') 189 - ->setValue($owner_value) 190 - ->setCaption( 191 - 'Tip: search for "Up For Grabs" to find unowned documents.')) 192 - ->appendChild( 193 - id(new AphrontFormTokenizerControl()) 194 - ->setName('subscribers') 195 - ->setLabel('Subscribers') 196 - ->setDatasource('/typeahead/common/users/') 197 - ->setValue($subscribers_value)) 198 - ->appendChild( 199 - id(new AphrontFormTokenizerControl()) 200 - ->setName('project') 201 - ->setLabel('Project') 202 - ->setDatasource('/typeahead/common/projects/') 203 - ->setValue($project_value)) 204 - ->appendChild( 205 - id(new AphrontFormSubmitControl()) 206 - ->setValue('Search')); 42 + public function buildSideNavView($for_app = false) { 43 + $viewer = $this->getRequest()->getUser(); 207 44 208 - $search_panel = new AphrontListFilterView(); 209 - $search_panel->appendChild($search_form); 45 + $nav = new AphrontSideNavFilterView(); 46 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 210 47 211 - require_celerity_resource('phabricator-search-results-css'); 48 + id(new PhabricatorSearchApplicationSearchEngine()) 49 + ->setViewer($viewer) 50 + ->addNavigationItems($nav->getMenu()); 212 51 213 - if ($query->getID()) { 52 + $nav->selectFilter(null); 214 53 215 - $limit = 20; 54 + return $nav; 55 + } 216 56 217 - $pager = new AphrontPagerView(); 218 - $pager->setURI($request->getRequestURI(), 'page'); 219 - $pager->setPageSize($limit); 220 - $pager->setOffset($request->getInt('page')); 57 + public function renderResultsList( 58 + array $results, 59 + PhabricatorSavedQuery $query) { 221 60 222 - $query->setParameter('limit', $limit + 1); 223 - $query->setParameter('offset', $pager->getOffset()); 61 + $viewer = $this->getRequest()->getUser(); 224 62 225 - $engine = PhabricatorSearchEngineSelector::newSelector()->newEngine(); 226 - $results = $engine->executeSearch($query); 227 - $results = $pager->sliceResults($results); 63 + if ($results) { 64 + $objects = id(new PhabricatorObjectQuery()) 65 + ->setViewer($viewer) 66 + ->withPHIDs(mpull($results, 'getPHID')) 67 + ->execute(); 228 68 229 - // If there are any objects which match the query by name, and we're 230 - // not paging through the results, prefix the results with the named 231 - // objects. 232 - if (!$request->getInt('page')) { 233 - $named = id(new PhabricatorObjectQuery()) 234 - ->setViewer($user) 235 - ->withNames(array($query->getParameter('queyr'))) 236 - ->execute(); 237 - if ($named) { 238 - $results = array_merge(array_keys($named), $results); 239 - } 69 + $output = array(); 70 + foreach ($results as $phid => $handle) { 71 + $view = id(new PhabricatorSearchResultView()) 72 + ->setHandle($handle) 73 + ->setQuery($query) 74 + ->setObject(idx($objects, $phid)); 75 + $output[] = $view->render(); 240 76 } 241 77 242 - if ($results) { 243 - $handles = id(new PhabricatorHandleQuery()) 244 - ->setViewer($user) 245 - ->withPHIDs($results) 246 - ->execute(); 247 - $objects = id(new PhabricatorObjectQuery()) 248 - ->setViewer($user) 249 - ->withPHIDs($results) 250 - ->execute(); 251 - $results = array(); 252 - foreach ($handles as $phid => $handle) { 253 - $view = id(new PhabricatorSearchResultView()) 254 - ->setHandle($handle) 255 - ->setQuery($query) 256 - ->setObject(idx($objects, $phid)); 257 - $results[] = $view->render(); 258 - } 259 - 260 - $results = phutil_tag_div('phabricator-search-result-list', array( 261 - phutil_implode_html("\n", $results), 262 - phutil_tag_div('search-results-pager', $pager->render()), 263 - )); 264 - } else { 265 - $results = phutil_tag_div( 266 - 'phabricator-search-result-list', 267 - phutil_tag( 268 - 'p', 269 - array('class' => 'phabricator-search-no-results'), 270 - pht('No search results.'))); 271 - } 272 - $results = id(new PHUIBoxView()) 273 - ->addMargin(PHUI::MARGIN_LARGE) 274 - ->addPadding(PHUI::PADDING_LARGE) 275 - ->setShadow(true) 276 - ->appendChild($results) 277 - ->addClass('phabricator-search-result-box'); 78 + $results = phutil_tag_div( 79 + 'phabricator-search-result-list', 80 + $output); 278 81 } else { 279 - $results = null; 82 + $results = phutil_tag_div( 83 + 'phabricator-search-result-list', 84 + phutil_tag( 85 + 'p', 86 + array('class' => 'phabricator-search-no-results'), 87 + pht('No search results.'))); 280 88 } 281 89 282 - $crumbs = $this->buildApplicationCrumbs(); 283 - $crumbs->addTextCrumb(pht('Search')); 284 - 285 - return $this->buildApplicationPage( 286 - array( 287 - $crumbs, 288 - $search_panel, 289 - $results, 290 - ), 291 - array( 292 - 'title' => pht('Search Results'), 293 - 'device' => true, 294 - )); 90 + return id(new PHUIBoxView()) 91 + ->addMargin(PHUI::MARGIN_LARGE) 92 + ->addPadding(PHUI::PADDING_LARGE) 93 + ->setShadow(true) 94 + ->appendChild($results) 95 + ->addClass('phabricator-search-result-box'); 295 96 } 296 - 297 97 298 98 }
+45 -21
src/applications/search/engine/PhabricatorSearchEngineElastic.php
··· 95 95 $spec = array(); 96 96 $filter = array(); 97 97 98 - if ($query->getParameter('query') != '') { 98 + if (strlen($query->getParameter('query'))) { 99 99 $spec[] = array( 100 100 'field' => array( 101 101 'field.corpus' => $query->getParameter('query'), ··· 114 114 ); 115 115 } 116 116 117 - $rel_mapping = array( 118 - 'author' => PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, 119 - 'open' => PhabricatorSearchRelationship::RELATIONSHIP_OPEN, 120 - 'owner' => PhabricatorSearchRelationship::RELATIONSHIP_OWNER, 121 - 'subscribers' => PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER, 122 - 'project' => PhabricatorSearchRelationship::RELATIONSHIP_PROJECT, 123 - 'repository' => PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY, 117 + $relationship_map = array( 118 + PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR => 119 + $query->getParameter('authorPHIDs', array()), 120 + PhabricatorSearchRelationship::RELATIONSHIP_OWNER => 121 + $query->getParameter('ownerPHIDs', array()), 122 + PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER => 123 + $query->getParameter('subscriberPHIDs', array()), 124 + PhabricatorSearchRelationship::RELATIONSHIP_PROJECT => 125 + $query->getParameter('projectPHIDs', array()), 126 + PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY => 127 + $query->getParameter('repositoryPHIDs', array()), 124 128 ); 125 - foreach ($rel_mapping as $name => $field) { 126 - $param = $query->getParameter($name); 127 - if (is_array($param)) { 129 + 130 + $statuses = $query->getParameter('statuses', array()); 131 + $statuses = array_fuse($statuses); 132 + 133 + $rel_open = PhabricatorSearchRelationship::RELATIONSHIP_OPEN; 134 + $rel_closed = PhabricatorSearchRelationship::RELATIONSHIP_CLOSED; 135 + $rel_unowned = PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED; 136 + 137 + $include_open = !empty($statuses[$rel_open]); 138 + $include_closed = !empty($statuses[$rel_closed]); 139 + 140 + if ($include_open && !$include_closed) { 141 + $relationship_map[$rel_open] = true; 142 + } else if (!$include_open && $include_closed) { 143 + $relationship_map[$rel_closed] = true; 144 + } 145 + 146 + if ($query->getParameter('withUnowned')) { 147 + $relationship_map[$rel_unowned] = true; 148 + } 149 + 150 + foreach ($relationship_map as $field => $param) { 151 + if (is_array($param) && $param) { 128 152 $should = array(); 129 153 foreach ($param as $val) { 130 154 $should[] = array( ··· 177 201 } 178 202 179 203 public function executeSearch(PhabricatorSavedQuery $query) { 180 - $type = $query->getParameter('type'); 181 - if ($type) { 182 - $uri = "/phabricator/{$type}/_search"; 183 - } else { 184 - // Don't use '/phabricator/_search' for the case that there is something 185 - // else in the index (for example if 'phabricator' is only an alias to 186 - // some bigger index). 187 - $types = PhabricatorSearchAbstractDocument::getSupportedTypes(); 188 - $uri = '/phabricator/' . implode(',', array_keys($types)) . '/_search'; 204 + $types = $query->getParameter('types'); 205 + if (!$types) { 206 + $types = array_keys( 207 + PhabricatorSearchApplicationSearchEngine::getIndexableDocumentTypes()); 189 208 } 190 209 210 + // Don't use '/phabricator/_search' for the case that there is something 211 + // else in the index (for example if 'phabricator' is only an alias to 212 + // some bigger index). 213 + $uri = '/phabricator/'.implode(',', $types).'/_search'; 214 + 191 215 try { 192 216 $response = $this->executeRequest($uri, $this->buildSpec($query)); 193 217 } catch (HTTPFutureResponseStatusHTTP $ex) { 194 218 // elasticsearch probably uses Lucene query syntax: 195 219 // http://lucene.apache.org/core/3_6_1/queryparsersyntax.html 196 220 // Try literal search if operator search fails. 197 - if (!$query->getParameter('query')) { 221 + if (!strlen($query->getParameter('query'))) { 198 222 throw $ex; 199 223 } 200 224 $query = clone $query;
+52 -34
src/applications/search/engine/PhabricatorSearchEngineMySQL.php
··· 179 179 $q); 180 180 181 181 $field = $query->getParameter('field'); 182 - if ($field/* && $field != AdjutantQuery::FIELD_ALL*/) { 182 + if ($field) { 183 183 $where[] = qsprintf( 184 184 $conn_r, 185 185 'field.field = %s', ··· 192 192 $where[] = qsprintf($conn_r, 'document.phid != %s', $exclude); 193 193 } 194 194 195 - if ($query->getParameter('type')) { 195 + $types = $query->getParameter('types'); 196 + if ($types) { 196 197 if (strlen($q)) { 197 - // TODO: verify that this column actually does something useful in query 198 - // plans once we have nontrivial amounts of data. 199 198 $where[] = qsprintf( 200 199 $conn_r, 201 - 'field.phidType = %s', 202 - $query->getParameter('type')); 200 + 'field.phidType IN (%Ls)', 201 + $types); 203 202 } 204 203 $where[] = qsprintf( 205 204 $conn_r, 206 - 'document.documentType = %s', 207 - $query->getParameter('type')); 205 + 'document.documentType IN (%Ls)', 206 + $types); 208 207 } 209 208 210 209 $join[] = $this->joinRelationship( 211 210 $conn_r, 212 211 $query, 213 - 'author', 212 + 'authorPHIDs', 214 213 PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR); 215 214 216 - $join[] = $this->joinRelationship( 217 - $conn_r, 218 - $query, 219 - 'open', 220 - PhabricatorSearchRelationship::RELATIONSHIP_OPEN); 215 + $statuses = $query->getParameter('statuses', array()); 216 + $statuses = array_fuse($statuses); 217 + $open_rel = PhabricatorSearchRelationship::RELATIONSHIP_OPEN; 218 + $closed_rel = PhabricatorSearchRelationship::RELATIONSHIP_CLOSED; 219 + $include_open = !empty($statuses[$open_rel]); 220 + $include_closed = !empty($statuses[$closed_rel]); 221 221 222 - $join[] = $this->joinRelationship( 223 - $conn_r, 224 - $query, 225 - 'owner', 226 - PhabricatorSearchRelationship::RELATIONSHIP_OWNER); 222 + if ($include_open && !$include_closed) { 223 + $join[] = $this->joinRelationship( 224 + $conn_r, 225 + $query, 226 + 'statuses', 227 + $open_rel, 228 + true); 229 + } else if ($include_closed && !$include_open) { 230 + $join[] = $this->joinRelationship( 231 + $conn_r, 232 + $query, 233 + 'statuses', 234 + $closed_rel, 235 + true); 236 + } 237 + 238 + if ($query->getParameter('withUnowned')) { 239 + $join[] = $this->joinRelationship( 240 + $conn_r, 241 + $query, 242 + 'withUnowned', 243 + PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED, 244 + true); 245 + } else { 246 + $join[] = $this->joinRelationship( 247 + $conn_r, 248 + $query, 249 + 'ownerPHIDs', 250 + PhabricatorSearchRelationship::RELATIONSHIP_OWNER); 251 + } 227 252 228 253 $join[] = $this->joinRelationship( 229 254 $conn_r, 230 255 $query, 231 - 'subscribers', 256 + 'subscriberPHIDs', 232 257 PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER); 233 258 234 259 $join[] = $this->joinRelationship( 235 260 $conn_r, 236 261 $query, 237 - 'project', 262 + 'projectPHIDs', 238 263 PhabricatorSearchRelationship::RELATIONSHIP_PROJECT); 239 264 240 265 $join[] = $this->joinRelationship( ··· 283 308 AphrontDatabaseConnection $conn, 284 309 PhabricatorSavedQuery $query, 285 310 $field, 286 - $type) { 287 - 288 - $phids = $query->getParameter($field, array()); 289 - if (!$phids) { 290 - return null; 291 - } 292 - 293 - $is_existence = false; 294 - switch ($type) { 295 - case PhabricatorSearchRelationship::RELATIONSHIP_OPEN: 296 - $is_existence = true; 297 - break; 298 - } 311 + $type, 312 + $is_existence = false) { 299 313 300 314 $sql = qsprintf( 301 315 $conn, ··· 307 321 $type); 308 322 309 323 if (!$is_existence) { 324 + $phids = $query->getParameter($field, array()); 325 + if (!$phids) { 326 + return null; 327 + } 310 328 $sql .= qsprintf( 311 329 $conn, 312 330 ' AND %C.relatedPHID in (%Ls)',
-14
src/applications/search/index/PhabricatorSearchAbstractDocument.php
··· 1 1 <?php 2 2 3 - /** 4 - * @group search 5 - */ 6 3 final class PhabricatorSearchAbstractDocument { 7 4 8 5 private $phid; ··· 12 9 private $documentModified; 13 10 private $fields = array(); 14 11 private $relationships = array(); 15 - 16 - public static function getSupportedTypes() { 17 - return array( 18 - DifferentialPHIDTypeRevision::TYPECONST => 'Differential Revisions', 19 - PhabricatorRepositoryPHIDTypeCommit::TYPECONST => 'Repository Commits', 20 - ManiphestPHIDTypeTask::TYPECONST => 'Maniphest Tasks', 21 - PhrictionPHIDTypeDocument::TYPECONST => 'Phriction Documents', 22 - PhabricatorPeoplePHIDTypeUser::TYPECONST => 'Phabricator Users', 23 - PonderPHIDTypeQuestion::TYPECONST => 'Ponder Questions', 24 - ); 25 - } 26 12 27 13 public function setPHID($phid) { 28 14 $this->phid = $phid;
+178 -4
src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php
··· 6 6 public function buildSavedQueryFromRequest(AphrontRequest $request) { 7 7 $saved = new PhabricatorSavedQuery(); 8 8 9 + $saved->setParameter('query', $request->getStr('query')); 10 + $saved->setParameter( 11 + 'statuses', 12 + $this->readListFromRequest($request, 'statuses')); 13 + $saved->setParameter( 14 + 'types', 15 + $this->readListFromRequest($request, 'types')); 16 + 17 + $saved->setParameter( 18 + 'authorPHIDs', 19 + $this->readUsersFromRequest($request, 'authorPHIDs')); 20 + 21 + $saved->setParameter( 22 + 'ownerPHIDs', 23 + $this->readUsersFromRequest($request, 'ownerPHIDs')); 24 + 25 + $saved->setParameter( 26 + 'withUnowned', 27 + $this->readBoolFromRequest($request, 'withUnowned')); 28 + 29 + $saved->setParameter( 30 + 'subscriberPHIDs', 31 + $this->readPHIDsFromRequest($request, 'subscriberPHIDs')); 32 + 33 + $saved->setParameter( 34 + 'projectPHIDs', 35 + $this->readPHIDsFromRequest($request, 'projectPHIDs')); 36 + 9 37 return $saved; 10 38 } 11 39 12 40 public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 13 - $query = id(new PhabricatorSearchDocumentQuery()); 14 - 41 + $query = id(new PhabricatorSearchDocumentQuery()) 42 + ->withSavedQuery($saved); 15 43 return $query; 16 44 } 17 45 18 46 public function buildSearchForm( 19 47 AphrontFormView $form, 20 - PhabricatorSavedQuery $saved_query) { 21 - return; 48 + PhabricatorSavedQuery $saved) { 49 + 50 + $options = array(); 51 + $author_value = null; 52 + $owner_value = null; 53 + $subscribers_value = null; 54 + $project_value = null; 55 + 56 + $author_phids = $saved->getParameter('authorPHIDs', array()); 57 + $owner_phids = $saved->getParameter('ownerPHIDs', array()); 58 + $subscriber_phids = $saved->getParameter('subscriberPHIDs', array()); 59 + $project_phids = $saved->getParameter('projectPHIDs', array()); 60 + 61 + $all_phids = array_merge( 62 + $author_phids, 63 + $owner_phids, 64 + $subscriber_phids, 65 + $project_phids); 66 + 67 + $all_handles = id(new PhabricatorHandleQuery()) 68 + ->setViewer($this->requireViewer()) 69 + ->withPHIDs($all_phids) 70 + ->execute(); 71 + 72 + $author_handles = array_select_keys($all_handles, $author_phids); 73 + $owner_handles = array_select_keys($all_handles, $owner_phids); 74 + $subscriber_handles = array_select_keys($all_handles, $subscriber_phids); 75 + $project_handles = array_select_keys($all_handles, $project_phids); 76 + 77 + $with_unowned = $saved->getParameter('withUnowned', array()); 78 + 79 + $status_values = $saved->getParameter('statuses', array()); 80 + $status_values = array_fuse($status_values); 81 + 82 + $statuses = array( 83 + PhabricatorSearchRelationship::RELATIONSHIP_OPEN => pht('Open'), 84 + PhabricatorSearchRelationship::RELATIONSHIP_CLOSED => pht('Closed'), 85 + ); 86 + $status_control = id(new AphrontFormCheckboxControl()) 87 + ->setLabel(pht('Document Status')); 88 + foreach ($statuses as $status => $name) { 89 + $status_control->addCheckbox( 90 + 'statuses[]', 91 + $status, 92 + $name, 93 + isset($status_values[$status])); 94 + } 95 + 96 + $type_values = $saved->getParameter('types', array()); 97 + $type_values = array_fuse($type_values); 98 + 99 + $types = self::getIndexableDocumentTypes(); 100 + 101 + $types_control = id(new AphrontFormCheckboxControl()) 102 + ->setLabel(pht('Document Types')); 103 + foreach ($types as $type => $name) { 104 + $types_control->addCheckbox( 105 + 'types[]', 106 + $type, 107 + $name, 108 + isset($type_values[$type])); 109 + } 110 + 111 + $form 112 + ->appendChild( 113 + phutil_tag( 114 + 'input', 115 + array( 116 + 'type' => 'hidden', 117 + 'name' => 'jump', 118 + 'value' => 'no', 119 + ))) 120 + ->appendChild( 121 + id(new AphrontFormTextControl()) 122 + ->setLabel('Query') 123 + ->setName('query') 124 + ->setValue($saved->getParameter('query'))) 125 + ->appendChild($status_control) 126 + ->appendChild($types_control) 127 + ->appendChild( 128 + id(new AphrontFormTokenizerControl()) 129 + ->setName('authorPHIDs') 130 + ->setLabel('Authors') 131 + ->setDatasource('/typeahead/common/users/') 132 + ->setValue($author_handles)) 133 + ->appendChild( 134 + id(new AphrontFormTokenizerControl()) 135 + ->setName('ownerPHIDs') 136 + ->setLabel('Owners') 137 + ->setDatasource('/typeahead/common/searchowner/') 138 + ->setValue($owner_handles)) 139 + ->appendChild( 140 + id(new AphrontFormCheckboxControl()) 141 + ->addCheckbox( 142 + 'withUnowned', 143 + 1, 144 + pht('Show only unowned documents.'), 145 + $with_unowned)) 146 + ->appendChild( 147 + id(new AphrontFormTokenizerControl()) 148 + ->setName('subscriberPHIDs') 149 + ->setLabel('Subscribers') 150 + ->setDatasource('/typeahead/common/users/') 151 + ->setValue($subscriber_handles)) 152 + ->appendChild( 153 + id(new AphrontFormTokenizerControl()) 154 + ->setName('projectPHIDs') 155 + ->setLabel('In Any Project') 156 + ->setDatasource('/typeahead/common/projects/') 157 + ->setValue($project_handles)); 22 158 } 23 159 24 160 protected function getURI($path) { ··· 28 164 public function getBuiltinQueryNames() { 29 165 $names = array( 30 166 'all' => pht('All Documents'), 167 + 'open' => pht('Open Documents'), 168 + 'open-tasks' => pht('Open Tasks'), 31 169 ); 32 170 33 171 return $names; ··· 41 179 switch ($query_key) { 42 180 case 'all': 43 181 return $query; 182 + case 'open': 183 + return $query->setParameter('statuses', array('open')); 184 + case 'open-tasks': 185 + return $query 186 + ->setParameter('statuses', array('open')) 187 + ->setParameter('types', array(ManiphestPHIDTypeTask::TYPECONST)); 44 188 } 45 189 46 190 return parent::buildSavedQueryFromBuiltin($query_key); 47 191 } 192 + 193 + public static function getIndexableDocumentTypes() { 194 + // TODO: This is inelegant and not very efficient, but gets us reasonable 195 + // results. It would be nice to do this more elegantly. 196 + 197 + // TODO: We should hide types associated with applications the user can 198 + // not access. There's no reasonable way to do this right now. 199 + 200 + $indexers = id(new PhutilSymbolLoader()) 201 + ->setAncestorClass('PhabricatorSearchDocumentIndexer') 202 + ->loadObjects(); 203 + 204 + $types = PhabricatorPHIDType::getAllTypes(); 205 + 206 + $results = array(); 207 + foreach ($types as $type) { 208 + $typeconst = $type->getTypeConstant(); 209 + foreach ($indexers as $indexer) { 210 + $fake_phid = 'PHID-'.$typeconst.'-fake'; 211 + if ($indexer->shouldIndexDocumentByPHID($fake_phid)) { 212 + $results[$typeconst] = $type->getTypeName(); 213 + } 214 + } 215 + } 216 + 217 + asort($results); 218 + 219 + return $results; 220 + } 221 + 48 222 49 223 }
+55 -1
src/applications/search/query/PhabricatorSearchDocumentQuery.php
··· 3 3 final class PhabricatorSearchDocumentQuery 4 4 extends PhabricatorCursorPagedPolicyAwareQuery { 5 5 6 + private $savedQuery; 7 + 8 + public function withSavedQuery(PhabricatorSavedQuery $query) { 9 + $this->savedQuery = $query; 10 + return $this; 11 + } 12 + 6 13 protected function loadPage() { 7 - return array(); 14 + $phids = $this->loadDocumentPHIDsWithoutPolicyChecks(); 15 + 16 + $handles = id(new PhabricatorHandleQuery()) 17 + ->setViewer($this->getViewer()) 18 + ->withPHIDs($phids) 19 + ->execute(); 20 + 21 + // Retain engine order. 22 + $handles = array_select_keys($handles, $phids); 23 + 24 + return $handles; 25 + } 26 + 27 + protected function willFilterPage(array $handles) { 28 + foreach ($handles as $key => $handle) { 29 + if (!$handle->isComplete()) { 30 + unset($handles[$key]); 31 + continue; 32 + } 33 + if ($handle->getPolicyFiltered()) { 34 + unset($handles[$key]); 35 + continue; 36 + } 37 + } 38 + 39 + return $handles; 40 + } 41 + 42 + public function loadDocumentPHIDsWithoutPolicyChecks() { 43 + $query = id(clone($this->savedQuery)) 44 + ->setParameter('offset', $this->getOffset()) 45 + ->setParameter('limit', $this->getRawResultLimit()); 46 + 47 + $engine = PhabricatorSearchEngineSelector::newSelector()->newEngine(); 48 + 49 + return $engine->executeSearch($query); 8 50 } 9 51 10 52 public function getQueryApplicationClass() { 11 53 return 'PhabricatorApplicationSearch'; 54 + } 55 + 56 + protected function getPagingValue($result) { 57 + throw new Exception( 58 + pht( 59 + 'This query does not support cursor paging; it must be offset '. 60 + 'paged.')); 61 + } 62 + 63 + protected function nextPage(array $page) { 64 + $this->setOffset($this->getOffset() + count($page)); 65 + return $this; 12 66 } 13 67 14 68 }