jenkins-bot has submitted this change and it was merged.

Change subject: API for _suggest for Elasticsearch
......................................................................


API for _suggest for Elasticsearch

Task: T106129
Aggregate and resolve suggestions

This merges together all the suggest results, sorts by score,
takes the top $limit results and resolves those into a final result
of the page title and suggestion score.

I think the scores themselves arn't working right yet, always getting
either 1 or 0. But this code should all work right once that starts
returning a proper score.

Not sure if we will need a more complicated merge or not, tbd.

Change-Id: I9c36b9c02a23c30038b1f0d8e8b8a9e310829e7e
---
M CirrusSearch.php
M autoload.php
M i18n/en.json
M i18n/qqq.json
A includes/Api/Suggest.php
A includes/Api/SuggestIndex.php
M includes/Searcher.php
M maintenance/runSearch.php
M maintenance/updateSuggesterIndex.php
M tests/browser/features/step_definitions/search_steps.rb
A tests/browser/features/suggest_api.feature
M tests/browser/features/support/cirrus_search_api_helper.rb
M tests/browser/features/support/hooks.rb
M tests/jenkins/Jenkins.php
14 files changed, 391 insertions(+), 4 deletions(-)

Approvals:
  EBernhardson: Looks good to me, approved
  jenkins-bot: Verified

Objections:
  Cindy-the-browser-test-bot: There's a problem with this change, please improve



diff --git a/CirrusSearch.php b/CirrusSearch.php
index da85959..4caf7ce 100644
--- a/CirrusSearch.php
+++ b/CirrusSearch.php
@@ -785,6 +785,22 @@
  */
 $wgCirrusSearchUserTesting = array();
 
+/**
+ * Settings for completion suggestion options.
+ * See CirrusSearch\BuildDocument\SuggestBuilder and CirrusSearch\Searcher
+ * fields - set of suggestion fields to use
+ * fuzzy - fuzziness configuration (false for no fuzziness)
+ * See also: 
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html
+ */
+$wgCirrusSearchCompletionSettings = array(
+       "fields" => array(
+               "suggest", "suggest-stop"
+       ),
+       "fuzzy" => array(
+               "fuzziness" => 2
+       ),
+);
+
 $includes = __DIR__ . "/includes/";
 $apiDir = $includes . 'Api/';
 $buildDocument = $includes . 'BuildDocument/';
@@ -849,6 +865,7 @@
 $wgAPIModules['cirrus-config-dump'] = 'CirrusSearch\Api\ConfigDump';
 $wgAPIModules['cirrus-mapping-dump'] = 'CirrusSearch\Api\MappingDump';
 $wgAPIModules['cirrus-settings-dump'] = 'CirrusSearch\Api\SettingsDump';
+$wgAPIModules['cirrus-suggest'] = 'CirrusSearch\Api\Suggest';
 
 /**
  * Jenkins configuration required to get all the browser tests passing cleanly.
diff --git a/autoload.php b/autoload.php
index f40c246..df5da06 100644
--- a/autoload.php
+++ b/autoload.php
@@ -9,6 +9,8 @@
        'CirrusSearch\\Api\\FreezeWritesToCluster' => __DIR__ . 
'/includes/Api/FreezeWritesToCluster.php',
        'CirrusSearch\\Api\\MappingDump' => __DIR__ . 
'/includes/Api/MappingDump.php',
        'CirrusSearch\\Api\\SettingsDump' => __DIR__ . 
'/includes/Api/SettingsDump.php',
+       'CirrusSearch\\Api\\Suggest' => __DIR__ . '/includes/Api/Suggest.php',
+       'CirrusSearch\\Api\\SuggestIndex' => __DIR__ . 
'/includes/Api/SuggestIndex.php',
        'CirrusSearch\\BuildDocument\\Builder' => __DIR__ . 
'/includes/BuildDocument/Builder.php',
        'CirrusSearch\\BuildDocument\\FileDataBuilder' => __DIR__ . 
'/includes/BuildDocument/FileDataBuilder.php',
        'CirrusSearch\\BuildDocument\\IncomingsLinksScoringMethod' => __DIR__ . 
'/includes/BuildDocument/SuggestScoring.php',
diff --git a/i18n/en.json b/i18n/en.json
index b8e7f72..28ec5c6 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -19,6 +19,10 @@
        "apihelp-cirrus-config-dump-description": "Dump of CirrusSearch 
configuration.",
        "apihelp-cirrus-mapping-dump-description": "Dump of CirrusSearch 
mapping for this wiki.",
        "apihelp-cirrus-settings-dump-description": "Dump of CirrusSearch 
settings for this wiki.",
+       "apihelp-cirrus-suggest-description": "Suggest completions for search 
term.",
+       "apihelp-cirrus-suggest-param-text": "Search term to provide 
completions for.",
+       "apihelp-cirrus-suggest-param-limit": "How many results to return.",
+       "apihelp-cirrus-suggest-param-context": "Search context limiting the 
search, e.g. geo or category.",
        "cirrussearch-give-feedback": "Give us your feedback",
        "cirrussearch-morelikethis-settings": " #<!-- leave this line exactly 
as it is --> <pre>\n# This message lets you configure the settings of the 
\"more like this\" feature.\n# Changes to this take effect immediately.\n# The 
syntax is as follows:\n#   * Everything from a \"#\" character to the end of 
the line is a comment.\n#   * Every non-blank line is the setting name followed 
by a \":\" character followed by the setting value\n# The settings are:\n#   * 
min_doc_freq (integer): Minimum number of documents (per shard) that need a 
term for it to be considered.\n#   * max_doc_freq (integer): Maximum number of 
documents (per shard) that have a term for it to be considered.\n#              
     High frequency terms are generally \"stop words\".\n#   * max_query_terms 
(integer): Maximum number of terms to be considered. This value is limited to 
$wgCirrusSearchMoreLikeThisMaxQueryTermsLimit (100).\n#   * min_term_freq 
(integer): Minimum number of times the term appears in the input to doc to be 
considered. For small fields (title) this value should be 1.\n#   * 
percent_terms_to_match (float 0 to 1): The percentage of terms to match on. 
Defaults to 0.3 (30 percent).\n#   * min_word_len (integer): Minimal length of 
a term to be considered. Defaults to 0.\n#   * max_word_len (integer): The 
maximum word length above which words will be ignored. Defaults to unbounded 
(0).\n#   * fields (comma separated list of values): These are the fields to 
use. Allowed fields are title, text, auxiliary_text, opening_text, headings and 
all.\n#   * use_fields (true|false) : Tell the \"more like this\" query to use 
only the field data. Defaults to false: the system will extract the content of 
the text field to build the query.\n# Examples of good lines:\n# 
min_doc_freq:2\n# max_doc_freq:20000\n# max_query_terms:25\n# 
min_term_freq:2\n# percent_terms_to_match:0.3\n# min_word_len:2\n# 
max_word_len:40\n# fields:text,opening_text\n# use_fields:true\n# </pre> <!-- 
leave this line exactly as it is -->",
        "cirrussearch-didyoumean-settings": "  #<!-- leave this line exactly as 
it is --> <pre>\n# This message lets you configure the settings of the \"Did 
you mean\" suggestions.\n# See also 
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-phrase.html\n#
 Changes to this take effect immediately.\n# The syntax is as follows:\n#   * 
Everything from a \"#\" character to the end of the line is a comment.\n#   * 
Every non-blank line is the setting name followed by a \":\" character followed 
by the setting value\n# The settings are :\n#   * max_errors (integer): the 
maximum number of terms that will be considered misspelled in order to be 
corrected. 1 or 2.\n#   * confidence (float): The confidence level defines a 
factor applied to the input phrases score which is used as a threshold for 
other suggestion candidates. Only candidates that score higher than the 
threshold will be included in the result. For instance a confidence level of 
1.0 will only return suggestions that score higher than the input phrase. If 
set to 0.0 the best candidate are returned.\n#   * min_doc_freq (float 0 to 1): 
The minimal threshold in number of documents a suggestion should appear in.\n#  
                 High frequency terms are generally \"stop words\".\n#   * 
max_term_freq (float 0 to 1): The maximum threshold in number of documents in 
which a term can exist in order to be included.\n#   * prefix_length (integer): 
The minimal number of prefix characters that must match a term in order to be a 
suggestion.\n#   * suggest_mode (missing, popular, always): The suggest mode 
controls the way suggestions are included.\n# Examples of good lines:\n# 
max_errors:2\n# confidence:2.0\n# max_term_freq:0.5\n# min_doc_freq:0.01\n# 
prefix_length:2\n# suggest_mode:always\n#\n# </pre> <!-- leave this line 
exactly as it is -->",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 83707f9..f65074c 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -27,6 +27,10 @@
        "apihelp-cirrus-config-dump-description": 
"{{doc-apihelp-description|cirrus-config-dump}}",
        "apihelp-cirrus-mapping-dump-description": 
"{{doc-apihelp-description|cirrus-mapping-dump}}",
        "apihelp-cirrus-settings-dump-description": 
"{{doc-apihelp-description|cirrus-settings-dump}}",
+       "apihelp-cirrus-suggest-description": 
"{{doc-apihelp-description|cirrus-suggest}}",
+       "apihelp-cirrus-suggest-param-text": 
"{{doc-apihelp-param|cirrus-suggest|text}}",
+       "apihelp-cirrus-suggest-param-context": 
"{{doc-apihelp-param|cirrus-suggest|context}}",
+       "apihelp-cirrus-suggest-param-limit": 
"{{doc-apihelp-param|cirrus-suggest|limit}}",
        "cirrussearch-give-feedback": "Used as text for an feedback link shown 
at the end of Special:Search result 
([[mw:Extension:CirrusSearch|$wgCirrusSearchFeedbackLink]])",
        "cirrussearch-morelikethis-settings": "Settings for the More Like This 
query.\n\n\"More Like This\" is the English name of the feature. The feature is 
described at [[:mw:Help:CirrusSearch#Special prefixes]]. The prefix 
\"morelike\" cannot be translated anywhere, but the full name of the feature 
\"More Like This\" can be translated.\n\nDon't translate technical names like 
min_doc_freq, max_query_terms, true|false, field names title, text, 
auxiliary_text, opening_text, headings, all etc.\n\nFor a definition of 
\"stopwords\" see [[:w:en:Stop words|Stop words in Wikipedia]].",
        "cirrussearch-didyoumean-settings": "Settings for the \"Did You Mean?\" 
suggestions.\n\n\"Did You Mean?\" is the English name of the feature and can be 
translated. This feature is described at 
[[:mw:Help:CirrusSearch#Did_you_mean]].\n\nDon't translate technical names like 
max_errors, confidence, max_term_freq, min_doc_freq and suggest_mode.\n\n\"Stop 
words\" are words that are explicitly excluded from searching and statistics, 
usually because they are too frequent to be useful and meaningful. See 
[[:w:en:Stop words|stop words]] in the English Wikipedia.",
diff --git a/includes/Api/Suggest.php b/includes/Api/Suggest.php
new file mode 100644
index 0000000..8d5d056
--- /dev/null
+++ b/includes/Api/Suggest.php
@@ -0,0 +1,87 @@
+<?php
+namespace CirrusSearch\Api;
+
+use ApiBase;
+use CirrusSearch\Searcher;
+use RequestContext;
+
+/**
+ * Use ElasticSearch suggestion API
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+class Suggest extends ApiBase {
+
+       public function execute() {
+               $context = RequestContext::getMain();
+               $user = $context->getUser();
+               $searcher = new Searcher( 0, $this->getParameter( 'limit' ), 
null, $user );
+
+               $queryText = $this->getParameter( 'text' );
+               if ( !$queryText ) {
+                       return;
+               }
+
+               $contextString = $this->getParameter( 'context' );
+               if( $contextString ) {
+                       $context = @json_decode( $contextString, true );
+                       /*
+                        * Validate the context, must be in the form of:
+                        * {
+                        *   name: { foo: bar, baz: qux }
+                        *   name2: { foo: bar, baz: qux }
+                        * }
+                        *
+                        */
+                       if( !is_array( $context )) {
+                               $context = null;
+                       } else {
+                               foreach( $context as $name => $ctx ) {
+                                       if ( !is_array( $ctx ) ) {
+                                               $this->dieUsage( "Bad context 
element $name", 'cirrus-badcontext' );
+                                       }
+                               }
+                       }
+               } else {
+                       $context = null;
+               }
+
+               // TODO: add passing context here,
+               // see 
https://www.elastic.co/guide/en/elasticsearch/reference/current/suggester-context.html
+               $result = $searcher->suggest( $queryText, $context );
+               if($result->isOK()) {
+                       $this->getResult()->addValue( null, 'suggest', 
$result->getValue() );
+               } else {
+                       $this->getResult()->addValue( null, "error", 
$result->getErrorsArray());
+               }
+       }
+
+       public function getAllowedParams() {
+               return array(
+                       'text' => array(
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_REQUIRED => true,
+                       ),
+                       'context' => array(
+                               ApiBase::PARAM_TYPE => 'string',
+                       ),
+                       'limit' => array(
+                               ApiBase::PARAM_TYPE => 'integer',
+                               ApiBase::PARAM_DFLT => 5,
+                       ),
+               );
+       }
+}
diff --git a/includes/Api/SuggestIndex.php b/includes/Api/SuggestIndex.php
new file mode 100644
index 0000000..7e84891
--- /dev/null
+++ b/includes/Api/SuggestIndex.php
@@ -0,0 +1,34 @@
+<?php
+namespace CirrusSearch\Api;
+
+use ApiBase;
+
+/**
+ * Update ElasticSearch suggestion index
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+class SuggestIndex extends ApiBase {
+
+       public function execute() {
+               // FIXME: This is horrible, no good, very bad hack. Only for 
testing,
+               // and probably should be eventually replaced with something 
more sane.
+               $updaterScript = 
"extensions/CirrusSearch/maintenance/updateSuggesterIndex.php";
+               $this->getResult()->addValue( null, 'result',
+                       wfShellExecWithStderr( "unset REQUEST_METHOD; 
/usr/local/bin/mwscript $updaterScript --wiki " . wfWikiID() )
+               );
+       }
+}
\ No newline at end of file
diff --git a/includes/Searcher.php b/includes/Searcher.php
index 3a79d1d..341c804 100644
--- a/includes/Searcher.php
+++ b/includes/Searcher.php
@@ -1,6 +1,7 @@
 <?php
 
 namespace CirrusSearch;
+
 use Elastica;
 use Category;
 use CirrusSearch;
@@ -18,6 +19,7 @@
 use Title;
 use UsageException;
 use User;
+use Elastica\Request;
 
 /**
  * Performs searches using Elasticsearch.  Note that each instance of this 
class
@@ -773,6 +775,132 @@
        }
 
        /**
+        * Produce a set of completion suggestions for text using _suggest
+        * See 
https://www.elastic.co/guide/en/elasticsearch/reference/1.6/search-suggesters-completion.html
+        * @param string $text Search term
+        * @param array $context Context, see 
https://www.elastic.co/guide/en/elasticsearch/reference/current/suggester-context.html
+        * @return Status
+        */
+       public function suggest( $text, $context = null ) {
+               global $wgCirrusSearchCompletionSettings, 
$wgCirrusSearchSearchShardTimeout;
+
+               $this->term = $text;
+
+               $suggest = array( 'text' => $text );
+               foreach ( $wgCirrusSearchCompletionSettings[ 'fields' ] as 
$field ) {
+                       $suggest[$field] = array(
+                               'completion' => array(
+                                       'field' => $field,
+                               )
+                       );
+                       if ( $context ) {
+                               $suggest[$field]['completion']['context'] = 
$context;
+                       }
+
+                       if( is_array( $wgCirrusSearchCompletionSettings[ 
'fuzzy' ] ) ) {
+                               $suggest[$field."-fuzzy"] = array(
+                                       'completion' => array(
+                                               'field' => $field,
+                                               'fuzzy' => 
$wgCirrusSearchCompletionSettings[ 'fuzzy' ],
+                                       )
+                               );
+                               if ( $context ) {
+                                       
$suggest[$field."-fuzzy"]['completion']['context'] = $context;
+                               }
+                       }
+               }
+
+               $queryOptions = array();
+               $queryOptions[ 'timeout' ] = $wgCirrusSearchSearchShardTimeout[ 
'default' ];
+               Connection::setTimeout( $wgCirrusSearchSearchShardTimeout[ 
'default' ] );
+
+               $index = Connection::getIndex( $this->indexBaseName, 
Connection::TITLE_SUGGEST_TYPE );
+               $description = "completion suggest query for {query}";
+               $logContext = array(
+                       'query' => $text,
+               );
+               $searcher = $this;
+               $result = Util::doPoolCounterWork(
+                       'CirrusSearch-Search',
+                       $this->user,
+                       function() use( $searcher, $index, $description, 
$suggest, $logContext, $queryOptions ) {
+                               $searcher->start( $description, $logContext );
+                               try {
+                                       return $index->request( "_suggest", 
Request::POST, $suggest, $queryOptions );
+                               } catch ( 
\Elastica\Exception\ExceptionInterface $e ) {
+                                       return $searcher->failure( $e );
+                               }
+                       }
+               );
+               if( $result->isOk() ) {
+                       $result = $this->postProcessSuggest( $result, 
$this->limit );
+                       return $this->success( $result );
+               }
+               return $result;
+       }
+
+       /**
+        * merge top level multi-queries and resolve returned pageIds into 
Title objects.
+        *
+        * @param \Elastica\Response $response Response from elasticsearch 
_suggest api
+        * @param int $limit Maximum suggestions to return, -1 for unlimited
+        * @return Title[] List of suggested titles
+        */
+       protected function postProcessSuggest( \Elastica\Response $response, 
$limit = -1 ) {
+               $data = $response->getData();
+               unset( $data['_shards'] );
+
+               $suggestions = array();
+               foreach ( $data as $name => $results  ) {
+                       foreach ( $results as $suggested ) {
+                               foreach ( $suggested['options'] as $suggest ) {
+                                       $pageId = $suggest['text'];
+                                       if ( !isset( $suggestions[$pageId] ) ||
+                                               $suggest['score'] > 
$suggestions[$pageId]['score']
+                                       ) {
+                                               $suggestions[$pageId] = 
$suggest;
+                                       }
+                               }
+                       }
+               }
+
+               // simply sort by existing scores
+               usort( $suggestions, function ( $a, $b ) {
+                       return $b['score'] - $a['score'];
+               } );
+
+               if ( $limit > 0 ) {
+                       $suggestions = array_slice( $suggestions, 0, $limit );
+               }
+
+               // suggest currently returns page ids, we need to resolve those 
now
+               $pageIds = array();
+               foreach ( $suggestions as $suggestion ) {
+                       $pageIds[] = $suggestion['text'];
+               }
+
+               // doesn't guarantee to maintain order
+               $unsortedTitles = Title::newFromIDs( $pageIds );
+               $byId = array();
+               foreach ( $unsortedTitles as $title ) {
+                       $byId[$title->getArticleID()] = $title;
+               }
+
+               $retval = array();
+               foreach ( $suggestions as $suggestion ) {
+                       $pageId = $suggestion['text'];
+                       if ( isset( $byId[$pageId] ) ) {
+                               $retval[] = array(
+                                       'title' => (string)$byId[$pageId],
+                                       'score' => $suggestion['score'],
+                               );
+                       }
+               }
+
+               return $retval;
+       }
+
+       /**
         * Builds a match query against $field for $title.  $title is munged to 
make title matching better more
         * intuitive for users.
         * @param string $field field containing the title
@@ -1199,6 +1327,7 @@
                                'path' => $search->getPath(),
                                'params' => $search->getOptions(),
                                'query' => $query->toArray(),
+                               'options' => $queryOptions,
                        ) );
                }
 
diff --git a/maintenance/runSearch.php b/maintenance/runSearch.php
index ccf8069..4e5c357 100644
--- a/maintenance/runSearch.php
+++ b/maintenance/runSearch.php
@@ -5,6 +5,7 @@
 use CirrusSearch;
 use CirrusSearch\Searcher;
 use Status;
+use CirrusSearch\Search\ResultSet;
 
 /**
  * Run search queries provided on stdin
@@ -38,7 +39,7 @@
                parent::__construct();
                $this->addDescription( "Run one or more searches against the 
cluster. " .
                        "search queries are read from stdin." );
-               $this->addOption( 'type', 'What type of search to run, prefix 
or full_text. ' .
+               $this->addOption( 'type', 'What type of search to run, prefix, 
suggest or full_text. ' .
                        'defaults to full_text.', false, true );
                $this->addOption( 'options', 'A JSON object mapping from global 
variable to ' .
                        'its test value' );
@@ -94,9 +95,14 @@
                $data = array( 'query' => $query );
                $status = $this->searchFor( $query );
                if ( $status->isOK() ) {
-                       $data['rows'] = $status->getValue()->numRows();
+                       $value = $status->getValue();
+                       if ( $value instanceof ResultSet ) {
+                               $data['rows'] = $value->numRows();
+                       } elseif ( is_array ($value ) ) {
+                               $data['rows'] = count( $value );
+                       }
                } else {
-                       $data['error'] = $resultSet->getMessage()->text();
+                       $data['error'] = $status->getMessage()->text();
                }
                return json_encode( $data );
        }
@@ -124,6 +130,15 @@
                        $searcher = new Searcher( 0, 10 );
                        return $searcher->prefixSearch( $query );
 
+               case 'suggest':
+                       $searcher = new Searcher( 0, 10 );
+                       $result = $searcher->suggest( $query );
+                       if ( $result instanceof Status ) {
+                               return $result;
+                       } else {
+                               return Status::newGood( $result );
+                       }
+
                default:
                        $this->error( "\nERROR: Unknown search type 
$searchType\n" );
                        exit( 1 );
diff --git a/maintenance/updateSuggesterIndex.php 
b/maintenance/updateSuggesterIndex.php
index 838f0be..79225b2 100644
--- a/maintenance/updateSuggesterIndex.php
+++ b/maintenance/updateSuggesterIndex.php
@@ -153,6 +153,7 @@
                                "Message: $message\n" .
                                "Trace:\n" . $trace, 1 );
                }
+               $this->getIndex()->refresh();
        }
 
        private function deleteOldIndex() {
@@ -397,5 +398,5 @@
        }
 }
 
-$maintClass = "CirrusSearch\Maintenance\UpdateSuggesterIndex";
+$maintClass = 'CirrusSearch\Maintenance\UpdateSuggesterIndex';
 require_once RUN_MAINTENANCE_IF_MAIN;
diff --git a/tests/browser/features/step_definitions/search_steps.rb 
b/tests/browser/features/step_definitions/search_steps.rb
index 484e605..08feb47 100644
--- a/tests/browser/features/step_definitions/search_steps.rb
+++ b/tests/browser/features/step_definitions/search_steps.rb
@@ -56,6 +56,36 @@
 Then(/^the api should offer to search for pages containing (.*)$/) do |term|
   @api_result[0].should == term
 end
+When(/^I ask suggestion API for (.*)$/) do |search|
+  begin
+    @api_result = suggestions_for_api(search)
+  rescue MediawikiApi::ApiError => e
+    @api_error = e
+  end
+end
+When(/^I ask suggestion API at most (\d+) items? for (.*)$/) do |limit, search|
+  begin
+    @api_result = suggestions_for_api(search, limit)
+  rescue MediawikiApi::ApiError => e
+    @api_error = e
+  end
+end
+Then(/^the API should produce list containing (.*)/) do |term|
+  found = false
+  @api_result["suggest"].each do |el|
+    found = true if el["title"] == term
+  end
+  found.should == true
+end
+Then(/^the API should produce list starting with (.*)/) do |term|
+  @api_result["suggest"][0]["title"].should == term
+end
+Then(/^the API should produce list of length (\d+)/) do |length|
+  @api_result["suggest"].length.should == length.to_i
+end
+Then(/^the API should produce empty list/) do
+  @api_result["suggest"].should == []
+end
 When(/^I get api near matches for (.*)$/) do |search|
   begin
     @api_result = search_for(
@@ -467,6 +497,12 @@
     thaw: 1
   )
 end
+When(/^I reindex suggestions$/) do
+  api.action(
+    :'cirrus-suggest-index',
+    token_type: false
+  )
+end
 
 def within(seconds)
   end_time = Time.new + Integer(seconds)
diff --git a/tests/browser/features/suggest_api.feature 
b/tests/browser/features/suggest_api.feature
new file mode 100644
index 0000000..c4b203f
--- /dev/null
+++ b/tests/browser/features/suggest_api.feature
@@ -0,0 +1,34 @@
+#
+# This file is subject to the license terms in the COPYING file found in the
+# CirrusSearch top-level directory and at
+# 
https://git.wikimedia.org/blob/mediawiki%2Fextensions%2FCirrusSearch/HEAD/COPYING.
 No part of
+# CirrusSearch, including this file, may be copied, modified, propagated, or
+# distributed except according to the terms contained in the COPYING file.
+#
+# Copyright 2012-2014 by the Mediawiki developers. See the CREDITS file in the
+# CirrusSearch top-level directory and at
+# 
https://git.wikimedia.org/blob/mediawiki%2Fextensions%2FCirrusSearch/HEAD/CREDITS
+#
+@api @suggest
+Feature: Suggestion API test
+
+  Scenario: Search suggestions
+    When I ask suggestion API for main
+     Then the API should produce list containing Main Page
+
+  Scenario: Created pages suggestions
+    When I ask suggestion API for x-m
+      Then the API should produce list containing X-Men
+
+  Scenario: Nothing to suggest
+    When I ask suggestion API for jabberwocky
+      Then the API should produce empty list
+
+  Scenario: Ordering
+       When I ask suggestion API for x-m
+         Then the API should produce list starting with X-Men
+
+  Scenario: Ordering & limit
+       When I ask suggestion API at most 1 item for x-m
+         Then the API should produce list starting with X-Men
+         And the API should produce list of length 1
\ No newline at end of file
diff --git a/tests/browser/features/support/cirrus_search_api_helper.rb 
b/tests/browser/features/support/cirrus_search_api_helper.rb
index 630988a..4fa9a80 100644
--- a/tests/browser/features/support/cirrus_search_api_helper.rb
+++ b/tests/browser/features/support/cirrus_search_api_helper.rb
@@ -56,6 +56,16 @@
     )
   end
 
+  # Get suggestions for a particular string using the new suggestions api
+  def suggestions_for_api(search, limit = nil)
+    req = {}
+    req["limit"] = limit if limit
+    api.action(:'cirrus-suggest', req.merge(
+      text: search,
+      token_type: false
+    ))
+  end
+
   def sha1_for_image(title, current_api = nil)
     current_api ||= api
     existing = current_api.prop(
diff --git a/tests/browser/features/support/hooks.rb 
b/tests/browser/features/support/hooks.rb
index 90317a3..beed156 100644
--- a/tests/browser/features/support/hooks.rb
+++ b/tests/browser/features/support/hooks.rb
@@ -616,6 +616,19 @@
   end
 end
 
+suggest = false
+Before("@suggest") do
+  unless suggest
+    steps %(
+      Given a page named X-Men exists with contents The X-Men are a fictional 
team of superheroes
+        And a page named Xavier, Charles exists with contents Professor 
Charles Francis Xavier (also known as Professor X) is the founder of [[X-Men]]
+        And a page named X-Force exists with contents X-Force is a fictional 
team of of [[X-Men]]
+        And I reindex suggestions
+    )
+    suggest = true
+  end
+end
+
 # Prevents api tests from generating fail screenshots.  Must come after all 
the above hooks
 # because some of them use the browser to set the preconditions necessary for 
api tests.
 Before("@api") do
diff --git a/tests/jenkins/Jenkins.php b/tests/jenkins/Jenkins.php
index 43b7486..1bc555c 100644
--- a/tests/jenkins/Jenkins.php
+++ b/tests/jenkins/Jenkins.php
@@ -77,6 +77,7 @@
 
 // Test only API action to expose freezing/thawing writes to the elasticsearch 
cluster
 $wgAPIModules['cirrus-freeze-writes'] = 
'CirrusSearch\Api\FreezeWritesToCluster';
+$wgAPIModules['cirrus-suggest-index'] = 'CirrusSearch\Api\SuggestIndex';
 // Bring the ElasticWrite backoff down to between 2^-1 and 2^3 seconds during 
browser tests
 $wgCirrusSearchWriteBackoffExponent = -1;
 

-- 
To view, visit https://gerrit.wikimedia.org/r/230927
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I9c36b9c02a23c30038b1f0d8e8b8a9e310829e7e
Gerrit-PatchSet: 30
Gerrit-Project: mediawiki/extensions/CirrusSearch
Gerrit-Branch: master
Gerrit-Owner: Smalyshev <[email protected]>
Gerrit-Reviewer: Chad <[email protected]>
Gerrit-Reviewer: Cindy-the-browser-test-bot <[email protected]>
Gerrit-Reviewer: DCausse <[email protected]>
Gerrit-Reviewer: EBernhardson <[email protected]>
Gerrit-Reviewer: Manybubbles <[email protected]>
Gerrit-Reviewer: MaxSem <[email protected]>
Gerrit-Reviewer: Smalyshev <[email protected]>
Gerrit-Reviewer: Tjones <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to