]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9075 “more” skips first 6 results, in api/components/suggestions
authorDaniel Schwarz <daniel.schwarz@sonarsource.com>
Wed, 10 May 2017 13:54:47 +0000 (15:54 +0200)
committerDaniel Schwarz <bartfastiel@users.noreply.github.com>
Thu, 11 May 2017 07:31:11 +0000 (09:31 +0200)
server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentHitsPerQualifier.java
server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java
server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexQuery.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexQueryTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/SuggestionsActionTest.java

index 6576b7fdac9ffd8c01dd19ccadf1e1f6a555b329..45069a2506cf0c3e3788a0f11236391b55a91ea4 100644 (file)
@@ -44,8 +44,4 @@ public class ComponentHitsPerQualifier {
   public long getTotalHits() {
     return totalHits;
   }
-
-  public long getNumberOfFurtherResults() {
-    return Math.max(totalHits - hits.size(), 0L);
-  }
 }
index 60d288028a90d6597ef7d5af1f3a39333b6c7002..d03b2220fb37db31224935b91cb1a2bd5827e09a 100644 (file)
@@ -114,8 +114,9 @@ public class ComponentIndex {
       .setHighlighterEncoder("html")
       .setHighlighterPreTags("<mark>")
       .setHighlighterPostTags("</mark>")
-      .addHighlightedField(createHighlighter());
-    query.getLimit().ifPresent(sub::setSize);
+      .addHighlightedField(createHighlighter())
+      .setFrom(query.getSkip())
+      .setSize(query.getLimit());
     return sub.setFetchSource(false);
   }
 
index 74a62b0467781d08d894579700e46b6bdade081e..e314afce4db1326675a2af2a60405c44fd84baa3 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.component.index;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Optional;
 import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -35,6 +34,7 @@ public class ComponentIndexQuery {
   private final Collection<String> qualifiers;
   private final Set<String> recentlyBrowsedKeys;
   private final Set<String> favoriteKeys;
+  private final int skip;
   private final int limit;
 
   private ComponentIndexQuery(Builder builder) {
@@ -42,6 +42,7 @@ public class ComponentIndexQuery {
     this.qualifiers = requireNonNull(builder.qualifiers);
     this.recentlyBrowsedKeys = requireNonNull(builder.recentlyBrowsedKeys);
     this.favoriteKeys = requireNonNull(builder.favoriteKeys);
+    this.skip = builder.skip;
     this.limit = builder.limit;
   }
 
@@ -57,8 +58,12 @@ public class ComponentIndexQuery {
     return recentlyBrowsedKeys;
   }
 
-  public Optional<Integer> getLimit() {
-    return Optional.ofNullable(limit);
+  public int getSkip() {
+    return skip;
+  }
+
+  public int getLimit() {
+    return limit;
   }
 
   public static Builder builder() {
@@ -74,6 +79,7 @@ public class ComponentIndexQuery {
     private Collection<String> qualifiers = Collections.emptyList();
     private Set<String> recentlyBrowsedKeys = Collections.emptySet();
     private Set<String> favoriteKeys = Collections.emptySet();
+    private int skip = 0;
     private int limit = DEFAULT_LIMIT;
 
     private Builder() {
@@ -100,6 +106,12 @@ public class ComponentIndexQuery {
       return this;
     }
 
+    public Builder setSkip(int skip) {
+      checkArgument(limit > 0, "Skip has to be strictly positive: %s", limit);
+      this.skip = skip;
+      return this;
+    }
+
     public Builder setLimit(int limit) {
       checkArgument(limit > 0, "Limit has to be strictly positive: %s", limit);
       this.limit = limit;
index 12328d0f6556185e80239e6d0bac9c12760868e5..8c4ea02e2c952e4653a7fe8277b6c79a55f0723b 100644 (file)
@@ -63,6 +63,7 @@ import static java.util.Optional.ofNullable;
 import static org.sonar.api.web.UserRole.USER;
 import static org.sonar.core.util.stream.MoreCollectors.toList;
 import static org.sonar.core.util.stream.MoreCollectors.toSet;
+import static org.sonar.server.component.index.ComponentIndexQuery.DEFAULT_LIMIT;
 import static org.sonar.server.es.DefaultIndexSettings.MINIMUM_NGRAM_LENGTH;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.WsComponents.SuggestionsWsResponse.Organization;
@@ -77,7 +78,7 @@ public class SuggestionsAction implements ComponentsWsAction {
   static final String SHORT_INPUT_WARNING = "short_input";
   private static final int MAXIMUM_RECENTLY_BROWSED = 50;
 
-  static final int EXTENDED_LIMIT = 20;
+  private static final int EXTENDED_LIMIT = 20;
 
   private final ComponentIndex index;
   private final FavoriteFinder favoriteFinder;
@@ -117,7 +118,7 @@ public class SuggestionsAction implements ComponentsWsAction {
       .setExampleValue("sonar");
 
     action.createParam(PARAM_MORE)
-      .setDescription("Category, for which to display " + EXTENDED_LIMIT + " instead of " + ComponentIndexQuery.DEFAULT_LIMIT + " results")
+      .setDescription("Category, for which to display the next " + EXTENDED_LIMIT + " results (skipping the first " + DEFAULT_LIMIT + " results)")
       .setPossibleValues(stream(SuggestionCategory.values()).map(SuggestionCategory::getName).toArray(String[]::new))
       .setSince("6.4");
 
@@ -136,7 +137,9 @@ public class SuggestionsAction implements ComponentsWsAction {
     String more = wsRequest.param(PARAM_MORE);
     Set<String> recentlyBrowsedKeys = getRecentlyBrowsedKeys(wsRequest);
     List<String> qualifiers = getQualifiers(more);
-    SuggestionsWsResponse searchWsResponse = loadSuggestions(query, more, recentlyBrowsedKeys, qualifiers);
+    int skip = more == null ? 0 : DEFAULT_LIMIT;
+    int limit = more == null ? DEFAULT_LIMIT : EXTENDED_LIMIT;
+    SuggestionsWsResponse searchWsResponse = loadSuggestions(query, skip, limit, recentlyBrowsedKeys, qualifiers);
     writeProtobuf(searchWsResponse, wsRequest, wsResponse);
   }
 
@@ -148,17 +151,17 @@ public class SuggestionsAction implements ComponentsWsAction {
     return new HashSet<>(recentlyBrowsedParam);
   }
 
-  private SuggestionsWsResponse loadSuggestions(@Nullable String query, String more, Set<String> recentlyBrowsedKeys, List<String> qualifiers) {
+  private SuggestionsWsResponse loadSuggestions(@Nullable String query, int skip, int limit, Set<String> recentlyBrowsedKeys, List<String> qualifiers) {
     if (query == null) {
-      return loadSuggestionsWithoutSearch(more, recentlyBrowsedKeys, qualifiers);
+      return loadSuggestionsWithoutSearch(skip, limit, recentlyBrowsedKeys, qualifiers);
     }
-    return loadSuggestionsWithSearch(query, more, recentlyBrowsedKeys, qualifiers);
+    return loadSuggestionsWithSearch(query, skip, limit, recentlyBrowsedKeys, qualifiers);
   }
 
   /**
    * we are generating suggestions, by using (1) favorites and (2) recently browsed components (without searchin in Elasticsearch)
    */
-  private SuggestionsWsResponse loadSuggestionsWithoutSearch(@Nullable String more, Set<String> recentlyBrowsedKeys, List<String> qualifiers) {
+  private SuggestionsWsResponse loadSuggestionsWithoutSearch(int skip, int limit, Set<String> recentlyBrowsedKeys, List<String> qualifiers) {
     List<ComponentDto> favoriteDtos = favoriteFinder.list();
     if (favoriteDtos.isEmpty() && recentlyBrowsedKeys.isEmpty()) {
       return newBuilder().build();
@@ -179,12 +182,12 @@ public class SuggestionsAction implements ComponentsWsAction {
       Comparator<ComponentDto> favoriteComparator = Comparator.comparing(c -> favoriteUuids.contains(c.uuid()) ? -1 : +1);
       Comparator<ComponentDto> comparator = favoriteComparator.thenComparing(ComponentDto::name);
 
-      int limit = more == null ? ComponentIndexQuery.DEFAULT_LIMIT : EXTENDED_LIMIT;
       ComponentIndexResults componentsPerQualifiers = ComponentIndexResults.newBuilder().setQualifiers(
         qualifiers.stream().map(q -> {
           List<ComponentHit> hits = componentsPerQualifier.get(q)
             .stream()
             .sorted(comparator)
+            .skip(skip)
             .limit(limit)
             .map(ComponentDto::uuid)
             .map(ComponentHit::new)
@@ -192,21 +195,20 @@ public class SuggestionsAction implements ComponentsWsAction {
           int totalHits = componentsPerQualifier.size();
           return new ComponentHitsPerQualifier(q, hits, totalHits);
         })).build();
-      return buildResponse(recentlyBrowsedKeys, favoriteUuids, componentsPerQualifiers, dbSession, componentDtos.stream()).build();
+      return buildResponse(recentlyBrowsedKeys, favoriteUuids, componentsPerQualifiers, dbSession, componentDtos.stream(), skip + limit).build();
     }
   }
 
-  private SuggestionsWsResponse loadSuggestionsWithSearch(String query, @Nullable String more, Set<String> recentlyBrowsedKeys, List<String> qualifiers) {
+  private SuggestionsWsResponse loadSuggestionsWithSearch(String query, int skip, int limit, Set<String> recentlyBrowsedKeys, List<String> qualifiers) {
     List<ComponentDto> favorites = favoriteFinder.list();
     Set<String> favoriteKeys = favorites.stream().map(ComponentDto::getKey).collect(MoreCollectors.toSet(favorites.size()));
     ComponentIndexQuery.Builder queryBuilder = ComponentIndexQuery.builder()
       .setQuery(query)
       .setRecentlyBrowsedKeys(recentlyBrowsedKeys)
       .setFavoriteKeys(favoriteKeys)
-      .setQualifiers(qualifiers);
-    if (more != null) {
-      queryBuilder.setLimit(EXTENDED_LIMIT);
-    }
+      .setQualifiers(qualifiers)
+      .setSkip(skip)
+      .setLimit(limit);
     ComponentIndexResults componentsPerQualifiers = searchInIndex(queryBuilder.build());
     if (componentsPerQualifiers.isEmpty()) {
       return newBuilder().build();
@@ -219,7 +221,7 @@ public class SuggestionsAction implements ComponentsWsAction {
         .collect(toSet());
       Stream<ComponentDto> componentDtoStream = dbClient.componentDao().selectByUuids(dbSession, componentUuids).stream();
       Set<String> favoriteUuids = favorites.stream().map(ComponentDto::uuid).collect(MoreCollectors.toSet(favorites.size()));
-      SuggestionsWsResponse.Builder searchWsResponse = buildResponse(recentlyBrowsedKeys, favoriteUuids, componentsPerQualifiers, dbSession, componentDtoStream);
+      SuggestionsWsResponse.Builder searchWsResponse = buildResponse(recentlyBrowsedKeys, favoriteUuids, componentsPerQualifiers, dbSession, componentDtoStream, skip + limit);
       getWarning(query).ifPresent(searchWsResponse::setWarning);
       return searchWsResponse.build();
     }
@@ -241,12 +243,12 @@ public class SuggestionsAction implements ComponentsWsAction {
   }
 
   private SuggestionsWsResponse.Builder buildResponse(Set<String> recentlyBrowsedKeys, Set<String> favoriteUuids, ComponentIndexResults componentsPerQualifiers,
-    DbSession dbSession, Stream<ComponentDto> stream) {
+    DbSession dbSession, Stream<ComponentDto> stream, int coveredItems) {
     Map<String, ComponentDto> componentsByUuids = stream
       .collect(MoreCollectors.uniqueIndex(ComponentDto::uuid));
     Map<String, OrganizationDto> organizationsByUuids = loadOrganizations(dbSession, componentsByUuids.values());
     Map<String, ComponentDto> projectsByUuids = loadProjects(dbSession, componentsByUuids.values());
-    return toResponse(componentsPerQualifiers, recentlyBrowsedKeys, favoriteUuids, organizationsByUuids, componentsByUuids, projectsByUuids);
+    return toResponse(componentsPerQualifiers, recentlyBrowsedKeys, favoriteUuids, organizationsByUuids, componentsByUuids, projectsByUuids, coveredItems);
   }
 
   private Map<String, ComponentDto> loadProjects(DbSession dbSession, Collection<ComponentDto> components) {
@@ -271,18 +273,18 @@ public class SuggestionsAction implements ComponentsWsAction {
   }
 
   private static SuggestionsWsResponse.Builder toResponse(ComponentIndexResults componentsPerQualifiers, Set<String> recentlyBrowsedKeys, Set<String> favoriteUuids,
-    Map<String, OrganizationDto> organizationsByUuids, Map<String, ComponentDto> componentsByUuids, Map<String, ComponentDto> projectsByUuids) {
+    Map<String, OrganizationDto> organizationsByUuids, Map<String, ComponentDto> componentsByUuids, Map<String, ComponentDto> projectsByUuids, int coveredItems) {
     if (componentsPerQualifiers.isEmpty()) {
       return newBuilder();
     }
     return newBuilder()
-      .addAllResults(toCategories(componentsPerQualifiers, recentlyBrowsedKeys, favoriteUuids, componentsByUuids, organizationsByUuids, projectsByUuids))
+      .addAllResults(toCategories(componentsPerQualifiers, recentlyBrowsedKeys, favoriteUuids, componentsByUuids, organizationsByUuids, projectsByUuids, coveredItems))
       .addAllOrganizations(toOrganizations(organizationsByUuids))
       .addAllProjects(toProjects(projectsByUuids));
   }
 
   private static List<Category> toCategories(ComponentIndexResults componentsPerQualifiers, Set<String> recentlyBrowsedKeys, Set<String> favoriteUuids,
-    Map<String, ComponentDto> componentsByUuids, Map<String, OrganizationDto> organizationByUuids, Map<String, ComponentDto> projectsByUuids) {
+    Map<String, ComponentDto> componentsByUuids, Map<String, OrganizationDto> organizationByUuids, Map<String, ComponentDto> projectsByUuids, int coveredItems) {
     return componentsPerQualifiers.getQualifiers().map(qualifier -> {
 
       List<Suggestion> suggestions = qualifier.getHits().stream()
@@ -291,7 +293,7 @@ public class SuggestionsAction implements ComponentsWsAction {
 
       return Category.newBuilder()
         .setQ(qualifier.getQualifier())
-        .setMore(qualifier.getNumberOfFurtherResults())
+        .setMore(Math.max(0, qualifier.getTotalHits() - coveredItems))
         .addAllItems(suggestions)
         .build();
     }).collect(toList());
index d1f51eaaaecaa70b951a60fca25f75df42083241..7192e929fc8f69445f0050f21315f04c2c8f31f7 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.server.component.index;
 
-import java.util.Optional;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -79,6 +78,6 @@ public class ComponentIndexQueryTest {
     ComponentIndexQuery query = ComponentIndexQuery.builder().setQuery("ab")
       .setLimit(1).build();
 
-    assertThat(query.getLimit()).isEqualTo(Optional.of(1));
+    assertThat(query.getLimit()).isEqualTo(1);
   }
 }
index 2ffdd06bdf3329b91ceb9fcc87e91efc03b96843..9247f5cb0ef87c24bcf01a30731069bc1d9fce81 100644 (file)
@@ -63,8 +63,6 @@ import static org.mockito.Mockito.mock;
 import static org.sonar.api.web.UserRole.USER;
 import static org.sonar.db.component.ComponentTesting.newModuleDto;
 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
-import static org.sonar.server.component.index.ComponentIndexQuery.DEFAULT_LIMIT;
-import static org.sonar.server.component.ws.SuggestionsAction.EXTENDED_LIMIT;
 import static org.sonar.server.component.ws.SuggestionsAction.PARAM_MORE;
 import static org.sonar.server.component.ws.SuggestionsAction.PARAM_QUERY;
 import static org.sonar.server.component.ws.SuggestionsAction.PARAM_RECENTLY_BROWSED;
@@ -274,8 +272,7 @@ public class SuggestionsActionTest {
         tuple("Bravo", true, false),
         tuple("Delta", true, false),
         tuple("Alpha", false, true),
-        tuple("Charlie", false, true)
-    );
+        tuple("Charlie", false, true));
   }
 
   @Test
@@ -475,26 +472,52 @@ public class SuggestionsActionTest {
   }
 
   @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);
+  public void should_not_propose_to_show_more_results_if_0_projects_are_found() throws Exception {
+    check_proposal_to_show_more_results(0, 0, 0L, null);
+  }
+
+  @Test
+  public void should_not_propose_to_show_more_results_if_5_projects_are_found() throws Exception {
+    check_proposal_to_show_more_results(5, 5, 0L, null);
   }
 
   @Test
   public void should_not_propose_to_show_more_results_if_6_projects_are_found() throws Exception {
-    check_proposal_to_show_more_results(6, DEFAULT_LIMIT, 0L, null);
+    check_proposal_to_show_more_results(6, 6, 0L, null);
   }
 
   @Test
-  public void should_not_propose_to_show_more_results_if_5_projects_are_found() throws Exception {
-    check_proposal_to_show_more_results(5, DEFAULT_LIMIT, 0L, null);
+  public void should_propose_to_show_more_results_if_7_projects_are_found() throws Exception {
+    check_proposal_to_show_more_results(7, 6, 1L, null);
   }
 
   @Test
-  public void show_show_more_results_if_requested() throws Exception {
-    check_proposal_to_show_more_results(21, EXTENDED_LIMIT, 1L, SuggestionCategory.PROJECT);
+  public void show_more_results_if_requested_and_5_projects_are_found() throws Exception {
+    check_proposal_to_show_more_results(5, 0, 0L, SuggestionCategory.PROJECT);
   }
 
-  private void check_proposal_to_show_more_results(int numberOfProjects, int results, long numberOfMoreResults, @Nullable SuggestionCategory more) throws Exception {
+  @Test
+  public void show_more_results_if_requested_and_6_projects_are_found() throws Exception {
+    check_proposal_to_show_more_results(6, 0, 0L, SuggestionCategory.PROJECT);
+  }
+
+  @Test
+  public void show_more_results_if_requested_and_7_projects_are_found() throws Exception {
+    check_proposal_to_show_more_results(7, 1, 0L, SuggestionCategory.PROJECT);
+  }
+
+  @Test
+  public void show_more_results_if_requested_and_26_projects_are_found() throws Exception {
+    check_proposal_to_show_more_results(26, 20, 0L, SuggestionCategory.PROJECT);
+  }
+
+  @Test
+  public void show_more_results_if_requested_and_27_projects_are_found() throws Exception {
+    check_proposal_to_show_more_results(27, 20, 1L, SuggestionCategory.PROJECT);
+  }
+
+  private void check_proposal_to_show_more_results(int numberOfProjects, int expectedNumberOfResults, long expectedNumberOfMoreResults, @Nullable SuggestionCategory more)
+    throws Exception {
     String namePrefix = "MyProject";
 
     List<ComponentDto> projects = range(0, numberOfProjects)
@@ -511,21 +534,21 @@ public class SuggestionsActionTest {
     SuggestionsWsResponse response = request
       .executeProtobuf(SuggestionsWsResponse.class);
 
-    // assert match in qualifier "TRK"
-    assertThat(response.getResultsList())
-      .filteredOn(q -> q.getItemsCount() > 0)
-      .extracting(Category::getQ)
-      .containsExactly(Qualifiers.PROJECT);
-
     // include limited number of results in the response
     assertThat(response.getResultsList())
       .flatExtracting(Category::getItemsList)
-      .hasSize(Math.min(results, numberOfProjects));
+      .hasSize(expectedNumberOfResults);
 
     // indicate, that there are more results
-    assertThat(response.getResultsList())
-      .filteredOn(q -> q.getItemsCount() > 0)
-      .extracting(Category::getMore)
-      .containsExactly(numberOfMoreResults);
+    if (expectedNumberOfResults == 0 && expectedNumberOfMoreResults == 0) {
+      assertThat(response.getResultsList())
+        .filteredOn(q -> q.getItemsCount() > 0)
+        .isEmpty();
+    } else {
+      assertThat(response.getResultsList())
+        .filteredOn(q -> q.getItemsCount() > 0)
+        .extracting(Category::getMore)
+        .containsExactly(expectedNumberOfMoreResults);
+    }
   }
 }