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;
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;
.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");
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);
}
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();
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)
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();
.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();
}
}
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) {
}
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()
return Category.newBuilder()
.setQ(qualifier.getQualifier())
- .setMore(qualifier.getNumberOfFurtherResults())
+ .setMore(Math.max(0, qualifier.getTotalHits() - coveredItems))
.addAllItems(suggestions)
.build();
}).collect(toList());
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;
tuple("Bravo", true, false),
tuple("Delta", true, false),
tuple("Alpha", false, true),
- tuple("Charlie", false, true)
- );
+ tuple("Charlie", false, true));
}
@Test
}
@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)
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);
+ }
}
}