diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -103,7 +103,7 @@ 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', 'rsrc/css/application/search/application-search-view.css' => '66ee5d46', - 'rsrc/css/application/search/search-results.css' => '64ad079a', + 'rsrc/css/application/search/search-results.css' => '9fc45e8d', 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', @@ -794,7 +794,7 @@ 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => '8d40ae75', 'phabricator-remarkup-css' => '17c0fb37', - 'phabricator-search-results-css' => '64ad079a', + 'phabricator-search-results-css' => '9fc45e8d', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => '4383192f', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2833,6 +2833,7 @@ 'PhabricatorFulltextEngineExtensionModule' => 'applications/search/index/PhabricatorFulltextEngineExtensionModule.php', 'PhabricatorFulltextIndexEngineExtension' => 'applications/search/engineextension/PhabricatorFulltextIndexEngineExtension.php', 'PhabricatorFulltextInterface' => 'applications/search/interface/PhabricatorFulltextInterface.php', + 'PhabricatorFulltextResult' => 'applications/search/fulltextstorage/PhabricatorFulltextResult.php', 'PhabricatorFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorFulltextStorageEngine.php', 'PhabricatorFundApplication' => 'applications/fund/application/PhabricatorFundApplication.php', 'PhabricatorGDSetupCheck' => 'applications/config/check/PhabricatorGDSetupCheck.php', @@ -3807,6 +3808,8 @@ 'PhabricatorSearchRelationshipSourceController' => 'applications/search/controller/PhabricatorSearchRelationshipSourceController.php', 'PhabricatorSearchResultBucket' => 'applications/search/buckets/PhabricatorSearchResultBucket.php', 'PhabricatorSearchResultBucketGroup' => 'applications/search/buckets/PhabricatorSearchResultBucketGroup.php', + 'PhabricatorSearchResultEngineExtension' => 'applications/search/engineextension/PhabricatorSearchResultEngineExtension.php', + 'PhabricatorSearchResultHighlightsEngineExtension' => 'applications/search/engineextension/PhabricatorSearchResultHighlightsEngineExtension.php', 'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php', 'PhabricatorSearchSchemaSpec' => 'applications/search/storage/PhabricatorSearchSchemaSpec.php', 'PhabricatorSearchScopeSetting' => 'applications/settings/setting/PhabricatorSearchScopeSetting.php', @@ -7960,6 +7963,7 @@ 'PhabricatorFulltextEngineExtension' => 'Phobject', 'PhabricatorFulltextEngineExtensionModule' => 'PhabricatorConfigModule', 'PhabricatorFulltextIndexEngineExtension' => 'PhabricatorIndexEngineExtension', + 'PhabricatorFulltextResult' => 'PhabricatorPolicyInterface', 'PhabricatorFulltextStorageEngine' => 'Phobject', 'PhabricatorFundApplication' => 'PhabricatorApplication', 'PhabricatorGDSetupCheck' => 'PhabricatorSetupCheck', @@ -9127,6 +9131,8 @@ 'PhabricatorSearchRelationshipSourceController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchResultBucket' => 'Phobject', 'PhabricatorSearchResultBucketGroup' => 'Phobject', + 'PhabricatorSearchResultEngineExtension' => 'Phobject', + 'PhabricatorSearchResultHighlightsEngineExtension' => 'PhabricatorSearchResultEngineExtension', 'PhabricatorSearchResultView' => 'AphrontView', 'PhabricatorSearchSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorSearchScopeSetting' => 'PhabricatorInternalSetting', diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -524,6 +524,8 @@ if (empty($fulltext_results)) { $fulltext_results = array(null); + } else { + $fulltext_results = array_keys($fulltext_results); } return qsprintf( diff --git a/src/applications/search/engineextension/PhabricatorSearchResultEngineExtension.php b/src/applications/search/engineextension/PhabricatorSearchResultEngineExtension.php new file mode 100644 --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorSearchResultEngineExtension.php @@ -0,0 +1,63 @@ +getPhobjectClassConstant('EXTENSIONKEY'); + } + + final public function setViewer($viewer) { + $this->viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + abstract public function isExtensionEnabled(); + + abstract public function getExtensionName(); + + abstract public function canRenderItemView(PhabricatorFulltextResult $result); + + public function getExtensionOrder() { + return 5000; + } + + public function willRenderItemView(array $objects) { + return null; + } + + abstract public function renderItemView( + PHUIObjectItemView $item, + PhabricatorFulltextResult $result); + + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getExtensionKey') + ->setSortMethod('getExtensionOrder') + ->execute(); + } + + /** + * @return PhabricatorSearchResultEngineExtension[] + */ + final public static function getAllEnabledExtensions() { + $extensions = self::getAllExtensions(); + + foreach ($extensions as $key => $extension) { + if (!$extension->isExtensionEnabled()) { + unset($extensions[$key]); + } + } + + return $extensions; + } + + +} diff --git a/src/applications/search/engineextension/PhabricatorSearchResultHighlightsEngineExtension.php b/src/applications/search/engineextension/PhabricatorSearchResultHighlightsEngineExtension.php new file mode 100644 --- /dev/null +++ b/src/applications/search/engineextension/PhabricatorSearchResultHighlightsEngineExtension.php @@ -0,0 +1,42 @@ +getHighlights('body'); + if ($highlights) { + $highlight_view = phutil_tag_div('phui-oi-subhead', + array( + phutil_tag('span', array( + 'class' => 'visual-only phui-icon-view phui-font-fa fa-quote-left', + )), + ' ', + phutil_safe_html($highlights), + ' ... ', + )); + $item->appendChild($highlight_view); + + } + + return $item; + } + +} diff --git a/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php --- a/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php @@ -142,6 +142,10 @@ } private function buildSpec(PhabricatorSavedQuery $query) { + $field_title = PhabricatorSearchDocumentFieldType::FIELD_TITLE; + $field_body = PhabricatorSearchDocumentFieldType::FIELD_BODY; + $field_comment = PhabricatorSearchDocumentFieldType::FIELD_COMMENT; + $q = new PhabricatorElasticsearchQueryBuilder('bool'); $query_string = $query->getParameter('query'); if (strlen($query_string)) { @@ -153,9 +157,10 @@ 'simple_query_string' => array( 'query' => $query_string, 'fields' => array( - PhabricatorSearchDocumentFieldType::FIELD_TITLE.'.*', - PhabricatorSearchDocumentFieldType::FIELD_BODY.'.*', - PhabricatorSearchDocumentFieldType::FIELD_COMMENT.'.*', + $field_title.'.*', + $field_body, + $field_body.'.*', + $field_comment.'.*', ), 'default_operator' => 'AND', ), @@ -170,12 +175,12 @@ 'query' => $query_string, 'fields' => array( '*.raw', - PhabricatorSearchDocumentFieldType::FIELD_TITLE.'^4', - PhabricatorSearchDocumentFieldType::FIELD_BODY.'^3', - PhabricatorSearchDocumentFieldType::FIELD_COMMENT.'^1.2', + $field_title.'^4', + $field_body.'^3', + $field_comment.'^1.2', ), 'analyzer' => 'english_exact', - 'default_operator' => 'and', + 'default_operator' => 'AND', ), )); @@ -268,6 +273,20 @@ $spec['from'] = $offset; $spec['size'] = $limit; + $spec['highlight'] = array( + 'pre_tags' => array(''), + 'post_tags' => array(''), + 'fields' => array( + $field_body => array( + 'matched_fields' => array( + $field_body.'.raw', + $field_body.'.keywords', + $field_body.'.stems', + ), + ), + ), + ); + return $spec; } @@ -289,8 +308,18 @@ foreach ($this->service->getAllHostsForRole('read') as $host) { try { $response = $this->executeRequest($host, $uri, $spec); - $phids = ipull($response['hits']['hits'], '_id'); - return $phids; + + $hits = array(); + foreach ($response['hits']['hits'] as $hit) { + $phid = $hit['_id']; + $result = new PhabricatorFulltextResult($phid); + if (isset($hit['highlight'])) { + $result->setHighlights($hit['highlight']); + } + $hits[$phid] = $result; + } + // phlog($hits); + return $hits; } catch (Exception $e) { $exceptions[] = $e; } diff --git a/src/applications/search/fulltextstorage/PhabricatorFulltextResult.php b/src/applications/search/fulltextstorage/PhabricatorFulltextResult.php new file mode 100644 --- /dev/null +++ b/src/applications/search/fulltextstorage/PhabricatorFulltextResult.php @@ -0,0 +1,74 @@ +setPHID($phid); + } + + public function setPHID($phid) { + $this->phid = $phid; + $this->type = phid_get_type($phid); + return $this; + } + + public function getPHID() { + return $this->phid; + } + + public function getType() { + return $this->type; + } + + /** + * @return PhabricatorObjectHandle + */ + public function getHandle() { + return $this->handle; + } + + public function setHandle($handle) { + $this->handle = $handle; + return $this; + } + + public function setHighlights($highlights) { + if (!$highlights) { + return; + } + + foreach ($highlights as $field => $values) { + $this->fields[$field] = implode(' ... ', array_slice($values, 0, 2)); + } + } + + public function getHighlights($field = 'all') { + if ($field == 'all') { + return phutil_safe_html(implode('
', $this->fields)); + } else if (!isset($this->fields[$field])) { + return ''; + } + return phutil_safe_html($this->fields[$field]); + } + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + public function getCapabilities() { + return $this->handle->getCapabilities(); + } + + public function getPolicy($capability) { + return $this->handle->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->handle->hasAutomaticCapability($capability, $viewer); + } + +} diff --git a/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php b/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php --- a/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php +++ b/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php @@ -253,9 +253,9 @@ ->withPHIDs(mpull($results, 'getPHID')) ->execute(); - foreach ($results as $phid => $handle) { + foreach ($results as $phid => $result) { $view = id(new PhabricatorSearchResultView()) - ->setHandle($handle) + ->setFulltextResult($result) ->setQuery($query) ->setObject(idx($objects, $phid)) ->render(); diff --git a/src/applications/search/query/PhabricatorSearchDocumentQuery.php b/src/applications/search/query/PhabricatorSearchDocumentQuery.php --- a/src/applications/search/query/PhabricatorSearchDocumentQuery.php +++ b/src/applications/search/query/PhabricatorSearchDocumentQuery.php @@ -24,7 +24,8 @@ } protected function loadPage() { - $phids = $this->loadDocumentPHIDsWithoutPolicyChecks(); + $results = $this->loadDocumentHitsWithoutPolicyChecks(); + $phids = array_keys($results); $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->getViewer()) @@ -32,13 +33,14 @@ ->withPHIDs($phids) ->execute(); - // Retain engine order. - $handles = array_select_keys($handles, $phids); + foreach ($phids as $phid) { + $results[$phid]->setHandle($handles[$phid]); + } - return $handles; + return $results; } - protected function willFilterPage(array $handles) { + protected function willFilterPage(array $results) { // NOTE: This is used by the object selector dialog to exclude the object // you're looking at, so that, e.g., a task can't be set as a dependency // of itself in the UI. @@ -51,25 +53,26 @@ $exclude = array_fuse($exclude_phids); } - foreach ($handles as $key => $handle) { + foreach ($results as $key => $result) { + $handle = $result->getHandle(); if (!$handle->isComplete()) { - unset($handles[$key]); + unset($results[$key]); continue; } if ($handle->getPolicyFiltered()) { - unset($handles[$key]); + unset($results[$key]); continue; } if (isset($exclude[$handle->getPHID()])) { - unset($handles[$key]); + unset($results[$key]); continue; } } - return $handles; + return $results; } - public function loadDocumentPHIDsWithoutPolicyChecks() { + public function loadDocumentHitsWithoutPolicyChecks() { $query = id(clone($this->savedQuery)) ->setParameter('offset', $this->getOffset()) ->setParameter('limit', $this->getRawResultLimit()); diff --git a/src/applications/search/view/PhabricatorSearchResultView.php b/src/applications/search/view/PhabricatorSearchResultView.php --- a/src/applications/search/view/PhabricatorSearchResultView.php +++ b/src/applications/search/view/PhabricatorSearchResultView.php @@ -5,12 +5,19 @@ private $handle; private $query; private $object; + private $result; public function setHandle(PhabricatorObjectHandle $handle) { $this->handle = $handle; return $this; } + public function setFulltextResult(PhabricatorFulltextResult $result) { + $this->result = $result; + $this->setHandle($result->getHandle()); + return $this; + } + public function setQuery(PhabricatorSavedQuery $query) { $this->query = $query; return $this; @@ -46,6 +53,13 @@ $item->addAttribute(pht('Closed')); } + $ext = PhabricatorSearchResultEngineExtension::getAllEnabledExtensions(); + foreach ($ext as $extension) { + if ($extension->canRenderItemView($this->result)) { + $item = $extension->renderItemView($item, $this->result); + } + } + return $item; } diff --git a/webroot/rsrc/css/application/search/search-results.css b/webroot/rsrc/css/application/search/search-results.css --- a/webroot/rsrc/css/application/search/search-results.css +++ b/webroot/rsrc/css/application/search/search-results.css @@ -2,7 +2,7 @@ * @provides phabricator-search-results-css */ -.phui-oi-link strong { +.phui-oi-link strong, .phui-oi-subhead strong { color: {$fire}; text-decoration: underline; }