]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9072 provide short token warning for api/components/suggestions
authorDaniel Schwarz <daniel.schwarz@sonarsource.com>
Fri, 7 Apr 2017 13:31:27 +0000 (15:31 +0200)
committerDaniel Schwarz <bartfastiel@users.noreply.github.com>
Thu, 20 Apr 2017 07:48:52 +0000 (09:48 +0200)
server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java
server/sonar-server/src/main/java/org/sonar/server/es/DefaultIndexSettings.java
server/sonar-server/src/main/java/org/sonar/server/es/DefaultIndexSettingsElement.java
server/sonar-server/src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchFeature.java
server/sonar-server/src/main/resources/org/sonar/server/component/ws/components-example-suggestions.json
server/sonar-server/src/test/java/org/sonar/server/component/ws/SuggestionsActionTest.java
sonar-ws/src/main/protobuf/ws-components.proto

index 6a470d1ef0efdb8fe2b87d99cb9d393d2a53624a..d24611ae113c5cb8edce205fffdf0da7e7e1af9e 100644 (file)
@@ -24,6 +24,8 @@ import com.google.common.io.Resources;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
@@ -37,13 +39,16 @@ import org.sonar.db.organization.OrganizationDto;
 import org.sonar.server.component.index.ComponentIndex;
 import org.sonar.server.component.index.ComponentIndexQuery;
 import org.sonar.server.component.index.ComponentsPerQualifier;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeature;
 import org.sonarqube.ws.WsComponents.Component;
 import org.sonarqube.ws.WsComponents.SuggestionsWsResponse;
 import org.sonarqube.ws.WsComponents.SuggestionsWsResponse.Qualifier;
 
 import static com.google.common.base.Preconditions.checkState;
 import static java.util.Arrays.asList;
+import static java.util.Optional.ofNullable;
 import static org.sonar.core.util.stream.MoreCollectors.toList;
+import static org.sonar.server.es.DefaultIndexSettings.MINIMUM_NGRAM_LENGTH;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_SUGGESTIONS;
 
@@ -51,6 +56,7 @@ public class SuggestionsAction implements ComponentsWsAction {
 
   static final String PARAM_QUERY = "s";
   static final String PARAM_MORE = "more";
+  static final String SHORT_INPUT_WARNING = "short_input";
 
   private static final String[] QUALIFIERS = {
     Qualifiers.VIEW,
@@ -84,7 +90,8 @@ public class SuggestionsAction implements ComponentsWsAction {
 
     action.createParam(PARAM_QUERY)
       .setRequired(true)
-      .setDescription("Substring of project key (minimum 2 characters)")
+      .setDescription("Search query with a minimum of two characters. Can contain several search tokens, separated by spaces. " +
+        "Search tokens with only one character will be ignored.")
       .setExampleValue("sonar");
 
     action.createParam(PARAM_MORE)
@@ -99,6 +106,24 @@ public class SuggestionsAction implements ComponentsWsAction {
     String more = wsRequest.param(PARAM_MORE);
 
     ComponentIndexQuery.Builder queryBuilder = ComponentIndexQuery.builder().setQuery(query);
+
+    List<ComponentsPerQualifier> componentsPerQualifiers = getComponentsPerQualifiers(more, queryBuilder);
+    String warning = getWarning(query);
+
+    SuggestionsWsResponse searchWsResponse = toResponse(componentsPerQualifiers, warning);
+    writeProtobuf(searchWsResponse, wsRequest, wsResponse);
+  }
+
+  private static String getWarning(String query) {
+    List<String> tokens = ComponentTextSearchFeature.split(query).collect(Collectors.toList());
+    if (tokens.stream().anyMatch(token -> token.length() < MINIMUM_NGRAM_LENGTH)) {
+      return SHORT_INPUT_WARNING;
+    }
+    return null;
+  }
+
+  private List<ComponentsPerQualifier> getComponentsPerQualifiers(String more, ComponentIndexQuery.Builder queryBuilder) {
+    List<ComponentsPerQualifier> componentsPerQualifiers;
     if (more == null) {
       queryBuilder.setQualifiers(asList(QUALIFIERS))
         .setLimit(DEFAULT_LIMIT);
@@ -106,19 +131,19 @@ public class SuggestionsAction implements ComponentsWsAction {
       queryBuilder.setQualifiers(Collections.singletonList(more))
         .setLimit(EXTENDED_LIMIT);
     }
-    List<ComponentsPerQualifier> componentsPerQualifiers = searchInIndex(queryBuilder.build());
-    SuggestionsWsResponse searchWsResponse = toResponse(componentsPerQualifiers);
-    writeProtobuf(searchWsResponse, wsRequest, wsResponse);
+    componentsPerQualifiers = searchInIndex(queryBuilder.build());
+    return componentsPerQualifiers;
   }
 
   private List<ComponentsPerQualifier> searchInIndex(ComponentIndexQuery componentIndexQuery) {
     return index.search(componentIndexQuery);
   }
 
-  private SuggestionsWsResponse toResponse(List<ComponentsPerQualifier> componentsPerQualifiers) {
-    return SuggestionsWsResponse.newBuilder()
-      .addAllResults(getResultsOfAllQualifiers(componentsPerQualifiers))
-      .build();
+  private SuggestionsWsResponse toResponse(List<ComponentsPerQualifier> componentsPerQualifiers, @Nullable String warning) {
+    SuggestionsWsResponse.Builder builder = SuggestionsWsResponse.newBuilder()
+      .addAllResults(getResultsOfAllQualifiers(componentsPerQualifiers));
+    ofNullable(warning).ifPresent(builder::setWarning);
+    return builder.build();
   }
 
   private List<Qualifier> getResultsOfAllQualifiers(List<ComponentsPerQualifier> componentsPerQualifiers) {
index ec2902a1ecfed688e07366426d03a9926b70aac6..7f96c1bb6fe1ab2dbbed97ecb7e88d94cd9616fd 100644 (file)
@@ -25,6 +25,8 @@ import org.elasticsearch.common.settings.Settings;
 
 public class DefaultIndexSettings {
 
+  /** Minimum length of ngrams. */
+  public static final int MINIMUM_NGRAM_LENGTH = 2;
   /** Maximum length of ngrams. */
   public static final int MAXIMUM_NGRAM_LENGTH = 15;
 
index c6482b6c5a4580deac3c03a493c979a1d18facb8..d1b2d42535229a43d612dc8dfc5c0c6499e449d4 100644 (file)
@@ -37,6 +37,7 @@ import static org.sonar.server.es.DefaultIndexSettings.KEYWORD;
 import static org.sonar.server.es.DefaultIndexSettings.LOWERCASE;
 import static org.sonar.server.es.DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH;
 import static org.sonar.server.es.DefaultIndexSettings.MAX_GRAM;
+import static org.sonar.server.es.DefaultIndexSettings.MINIMUM_NGRAM_LENGTH;
 import static org.sonar.server.es.DefaultIndexSettings.MIN_GRAM;
 import static org.sonar.server.es.DefaultIndexSettings.PATTERN;
 import static org.sonar.server.es.DefaultIndexSettings.PORTER_STEM;
@@ -69,22 +70,13 @@ public enum DefaultIndexSettingsElement {
       set("stem_english_possessive", true);
     }
   },
-  EDGE_NGRAM_FILTER(FILTER) {
-
-    @Override
-    protected void setup() {
-      set(TYPE, "edge_ngram");
-      set(MIN_GRAM, 1);
-      set(MAX_GRAM, 15);
-    }
-  },
   NGRAM_FILTER(FILTER) {
 
     @Override
     protected void setup() {
       set(TYPE, "nGram");
-      set(MIN_GRAM, 2);
-      set(MAX_GRAM, 15);
+      set(MIN_GRAM, MINIMUM_NGRAM_LENGTH);
+      set(MAX_GRAM, MAXIMUM_NGRAM_LENGTH);
       setArray("token_chars", "letter", "digit", "punctuation", "symbol");
     }
   },
@@ -96,7 +88,7 @@ public enum DefaultIndexSettingsElement {
     @Override
     protected void setup() {
       set(TYPE, "nGram");
-      set(MIN_GRAM, 2);
+      set(MIN_GRAM, MINIMUM_NGRAM_LENGTH);
       set(MAX_GRAM, MAXIMUM_NGRAM_LENGTH);
       setArray("token_chars", "letter", "digit", "punctuation", "symbol");
     }
index 5410a7a4eea03bd0f5ed6d797ca6ac5c3c29f20d..2cfd5e363f7a5e90753ca93e5df1ea3ed5ba8a5c 100644 (file)
@@ -81,7 +81,7 @@ public enum ComponentTextSearchFeature {
 
   public abstract QueryBuilder getQuery(ComponentTextSearchQuery query);
 
-  protected Stream<String> split(String queryText) {
+  public static Stream<String> split(String queryText) {
     return Arrays.stream(
       queryText.split(DefaultIndexSettings.SEARCH_TERM_TOKENIZER_PATTERN))
       .filter(StringUtils::isNotEmpty);
index f04f6838439ad013377946c2ba1db31f0a1ff181..eefe65f4c5fb9a63d4d87155a94c68e98ac3dc03 100644 (file)
@@ -51,6 +51,7 @@ import static org.assertj.core.groups.Tuple.tuple;
 import static org.sonar.db.component.ComponentTesting.newProjectDto;
 import static org.sonar.server.component.ws.SuggestionsAction.DEFAULT_LIMIT;
 import static org.sonar.server.component.ws.SuggestionsAction.EXTENDED_LIMIT;
+import static org.sonar.server.component.ws.SuggestionsAction.SHORT_INPUT_WARNING;
 import static org.sonar.server.component.ws.SuggestionsAction.PARAM_MORE;
 import static org.sonar.server.component.ws.SuggestionsAction.PARAM_QUERY;
 
@@ -113,6 +114,32 @@ public class SuggestionsActionTest {
       .containsExactly(tuple(project.getKey(), organization.getKey()));
   }
 
+  @Test
+  public void must_not_search_if_no_valid_tokens_are_provided() throws Exception {
+    ComponentDto project = db.components().insertComponent(newProjectDto(organization).setName("SonarQube"));
+
+    componentIndexer.indexOnStartup(null);
+    authorizationIndexerTester.allowOnlyAnyone(project);
+
+    SuggestionsWsResponse response = actionTester.newRequest()
+      .setMethod("POST")
+      .setParam(PARAM_QUERY, "S o")
+      .executeProtobuf(SuggestionsWsResponse.class);
+
+    assertThat(response.getResultsList()).filteredOn(q -> q.getItemsCount() > 0).isEmpty();
+    assertThat(response.getWarning()).contains(SHORT_INPUT_WARNING);
+  }
+
+  @Test
+  public void should_warn_about_short_inputs() throws Exception {
+    SuggestionsWsResponse response = actionTester.newRequest()
+      .setMethod("POST")
+      .setParam(PARAM_QUERY, "validLongToken x")
+      .executeProtobuf(SuggestionsWsResponse.class);
+
+    assertThat(response.getWarning()).contains(SHORT_INPUT_WARNING);
+  }
+
   @Test
   public void should_propose_to_show_more_results_if_7_projects_are_found() throws Exception {
     check_proposal_to_show_more_results(7, DEFAULT_LIMIT, 1L, null);
index 3d289f86b73767ef113a1cd5452a5c02e8cd1b4d..70eee9bbda03fb5c7aa6045e3cc1238582ab4ce9 100644 (file)
@@ -48,7 +48,8 @@ message ShowWsResponse {
 
 // WS api/components/suggestions
 message SuggestionsWsResponse {
-  repeated Qualifier results = 2;
+  repeated Qualifier results = 1;
+  optional string warning = 2;
   
   message Qualifier {
        optional string q = 1;