*/
package org.sonar.server.component.ws;
+import com.google.common.collect.ListMultimap;
import com.google.common.html.HtmlEscapers;
import com.google.common.io.Resources;
import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
+import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.component.index.ComponentIndexResults;
import org.sonar.server.es.textsearch.ComponentTextSearchFeature;
import org.sonar.server.favorite.FavoriteFinder;
+import org.sonar.server.user.UserSession;
import org.sonarqube.ws.WsComponents.SuggestionsWsResponse;
import org.sonarqube.ws.WsComponents.SuggestionsWsResponse.Category;
import org.sonarqube.ws.WsComponents.SuggestionsWsResponse.Project;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
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.es.DefaultIndexSettings.MINIMUM_NGRAM_LENGTH;
static final String PARAM_MORE = "more";
static final String PARAM_RECENTLY_BROWSED = "recentlyBrowsed";
static final String SHORT_INPUT_WARNING = "short_input";
- private static final long MAXIMUM_RECENTLY_BROWSED = 50;
+ private static final int MAXIMUM_RECENTLY_BROWSED = 50;
static final int EXTENDED_LIMIT = 20;
private final ComponentIndex index;
private final FavoriteFinder favoriteFinder;
+ private final UserSession userSession;
private DbClient dbClient;
- public SuggestionsAction(DbClient dbClient, ComponentIndex index, FavoriteFinder favoriteFinder) {
+ public SuggestionsAction(DbClient dbClient, ComponentIndex index, FavoriteFinder favoriteFinder, UserSession userSession) {
this.dbClient = dbClient;
this.index = index;
this.favoriteFinder = favoriteFinder;
+ this.userSession = userSession;
}
@Override
.setSince("4.2")
.setInternal(true)
.setHandler(this)
- .setResponseExample(Resources.getResource(this.getClass(), "components-example-suggestions.json"));
+ .setResponseExample(Resources.getResource(this.getClass(), "components-example-suggestions.json"))
+ .setChangelog(new Change("6.4", "Parameter 's' is optional"));
action.createParam(PARAM_QUERY)
- .setRequired(true)
+ .setRequired(false)
.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");
+ " items will be used. Order is not taken into account.")
.setSince("6.4")
.setExampleValue("org.sonarsource:sonarqube,some.other:project")
- .setRequired(false);
+ .setRequired(false)
+ .setMaxValuesAllowed(MAXIMUM_RECENTLY_BROWSED);
}
@Override
public void handle(Request wsRequest, Response wsResponse) throws Exception {
String query = wsRequest.param(PARAM_QUERY);
String more = wsRequest.param(PARAM_MORE);
+ Set<String> recentlyBrowsedKeys = getRecentlyBrowsedKeys(wsRequest);
+ List<String> qualifiers = getQualifiers(more);
+ SuggestionsWsResponse searchWsResponse = loadSuggestions(query, more, recentlyBrowsedKeys, qualifiers);
+ writeProtobuf(searchWsResponse, wsRequest, wsResponse);
+ }
+
+ private static Set<String> getRecentlyBrowsedKeys(Request wsRequest) {
List<String> recentlyBrowsedParam = wsRequest.paramAsStrings(PARAM_RECENTLY_BROWSED);
- Set<String> recentlyBrowsedKeys;
if (recentlyBrowsedParam == null) {
- recentlyBrowsedKeys = emptySet();
- } else {
- recentlyBrowsedKeys = recentlyBrowsedParam.stream().limit(MAXIMUM_RECENTLY_BROWSED).collect(Collectors.toSet());
+ return emptySet();
+ }
+ return new HashSet<>(recentlyBrowsedParam);
+ }
+
+ private SuggestionsWsResponse loadSuggestions(@Nullable String query, String more, Set<String> recentlyBrowsedKeys, List<String> qualifiers) {
+ if (query == null) {
+ return loadSuggestionsWithoutSearch(more, recentlyBrowsedKeys, qualifiers);
}
- Set<String> favoriteKeys = favoriteFinder.list().stream().map(ComponentDto::getKey).collect(Collectors.toSet());
+ return loadSuggestionsWithSearch(query, more, 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) {
+ List<ComponentDto> favoriteDtos = favoriteFinder.list();
+ if (favoriteDtos.isEmpty() && recentlyBrowsedKeys.isEmpty()) {
+ return newBuilder().build();
+ }
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ Set<ComponentDto> componentDtos = new HashSet<>(favoriteDtos);
+ if (!recentlyBrowsedKeys.isEmpty()) {
+ componentDtos.addAll(dbClient.componentDao().selectByKeys(dbSession, recentlyBrowsedKeys));
+ }
+ ListMultimap<String, ComponentDto> componentsPerQualifier = componentDtos.stream()
+ .filter(c -> userSession.hasComponentPermission(USER, c))
+ .collect(MoreCollectors.index(ComponentDto::qualifier));
+ if (componentsPerQualifier.isEmpty()) {
+ return newBuilder().build();
+ }
+
+ Set<String> favoriteUuids = favoriteDtos.stream().map(ComponentDto::uuid).collect(MoreCollectors.toSet(favoriteDtos.size()));
+ 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)
+ .limit(limit)
+ .map(ComponentDto::uuid)
+ .map(ComponentHit::new)
+ .collect(MoreCollectors.toList(limit));
+ int totalHits = componentsPerQualifier.size();
+ return new ComponentHitsPerQualifier(q, hits, totalHits);
+ })).build();
+ return buildResponse(recentlyBrowsedKeys, favoriteUuids, componentsPerQualifiers, dbSession, componentDtos.stream()).build();
+ }
+ }
+ private SuggestionsWsResponse loadSuggestionsWithSearch(String query, @Nullable String more, 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);
-
- ComponentIndexResults componentsPerQualifiers = getComponentsPerQualifiers(more, queryBuilder);
- String warning = getWarning(query);
-
- SuggestionsWsResponse searchWsResponse = toResponse(componentsPerQualifiers, recentlyBrowsedKeys, favoriteKeys, warning);
- writeProtobuf(searchWsResponse, wsRequest, wsResponse);
+ .setFavoriteKeys(favoriteKeys)
+ .setQualifiers(qualifiers);
+ if (more != null) {
+ queryBuilder.setLimit(EXTENDED_LIMIT);
+ }
+ ComponentIndexResults componentsPerQualifiers = searchInIndex(queryBuilder.build());
+ if (componentsPerQualifiers.isEmpty()) {
+ return newBuilder().build();
+ }
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ Set<String> componentUuids = componentsPerQualifiers.getQualifiers()
+ .map(ComponentHitsPerQualifier::getHits)
+ .flatMap(Collection::stream)
+ .map(ComponentHit::getUuid)
+ .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);
+ getWarning(query).ifPresent(searchWsResponse::setWarning);
+ return searchWsResponse.build();
+ }
}
- private static String getWarning(String query) {
+ private static Optional<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 Optional.of(SHORT_INPUT_WARNING);
}
- return null;
+ return Optional.empty();
}
- private ComponentIndexResults getComponentsPerQualifiers(@Nullable String more, ComponentIndexQuery.Builder queryBuilder) {
- List<String> qualifiers;
+ private static List<String> getQualifiers(@Nullable String more) {
if (more == null) {
- qualifiers = stream(SuggestionCategory.values()).map(SuggestionCategory::getQualifier).collect(Collectors.toList());
- } else {
- qualifiers = singletonList(SuggestionCategory.getByName(more).getQualifier());
- queryBuilder.setLimit(EXTENDED_LIMIT);
+ return stream(SuggestionCategory.values()).map(SuggestionCategory::getQualifier).collect(Collectors.toList());
}
- queryBuilder.setQualifiers(qualifiers);
- return searchInIndex(queryBuilder.build());
+ return singletonList(SuggestionCategory.getByName(more).getQualifier());
+ }
+
+ private SuggestionsWsResponse.Builder buildResponse(Set<String> recentlyBrowsedKeys, Set<String> favoriteUuids, ComponentIndexResults componentsPerQualifiers, DbSession dbSession,
+ Stream<ComponentDto> stream) {
+ 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);
+ }
+
+ private Map<String, ComponentDto> loadProjects(DbSession dbSession, Collection<ComponentDto> components) {
+ Set<String> projectUuids = components.stream()
+ .filter(c -> !c.projectUuid().equals(c.uuid()))
+ .map(ComponentDto::projectUuid)
+ .collect(MoreCollectors.toSet());
+ return dbClient.componentDao().selectByUuids(dbSession, projectUuids).stream()
+ .collect(MoreCollectors.uniqueIndex(ComponentDto::uuid));
+ }
+
+ private Map<String, OrganizationDto> loadOrganizations(DbSession dbSession, Collection<ComponentDto> components) {
+ Set<String> organizationUuids = components.stream()
+ .map(ComponentDto::getOrganizationUuid)
+ .collect(MoreCollectors.toSet());
+ return dbClient.organizationDao().selectByUuids(dbSession, organizationUuids).stream()
+ .collect(MoreCollectors.uniqueIndex(OrganizationDto::getUuid));
}
private ComponentIndexResults searchInIndex(ComponentIndexQuery componentIndexQuery) {
return index.search(componentIndexQuery);
}
- private SuggestionsWsResponse toResponse(ComponentIndexResults componentsPerQualifiers, Set<String> recentlyBrowsedKeys, Set<String> favoriteKeys, @Nullable String warning) {
- SuggestionsWsResponse.Builder builder = newBuilder();
- if (!componentsPerQualifiers.isEmpty()) {
- Map<String, OrganizationDto> organizationsByUuids;
- Map<String, ComponentDto> componentsByUuids;
- Map<String, ComponentDto> projectsByUuids;
- try (DbSession dbSession = dbClient.openSession(false)) {
- Set<String> componentUuids = componentsPerQualifiers.getQualifiers()
- .map(ComponentHitsPerQualifier::getHits)
- .flatMap(Collection::stream)
- .map(ComponentHit::getUuid)
- .collect(toSet());
- componentsByUuids = dbClient.componentDao().selectByUuids(dbSession, componentUuids).stream()
- .collect(MoreCollectors.uniqueIndex(ComponentDto::uuid));
- Set<String> organizationUuids = componentsByUuids.values().stream()
- .map(ComponentDto::getOrganizationUuid)
- .collect(toSet());
- organizationsByUuids = dbClient.organizationDao().selectByUuids(dbSession, organizationUuids).stream()
- .collect(MoreCollectors.uniqueIndex(OrganizationDto::getUuid));
- Set<String> projectUuids = componentsByUuids.values().stream()
- .filter(c -> !c.projectUuid().equals(c.uuid()))
- .map(ComponentDto::projectUuid)
- .collect(toSet());
- projectsByUuids = dbClient.componentDao().selectByUuids(dbSession, projectUuids).stream()
- .collect(MoreCollectors.uniqueIndex(ComponentDto::uuid));
- }
- builder
- .addAllResults(toCategories(componentsPerQualifiers, recentlyBrowsedKeys, favoriteKeys, componentsByUuids, organizationsByUuids, projectsByUuids))
- .addAllOrganizations(toOrganizations(organizationsByUuids))
- .addAllProjects(toProjects(projectsByUuids));
+ 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) {
+ if (componentsPerQualifiers.isEmpty()) {
+ return newBuilder();
}
- ofNullable(warning).ifPresent(builder::setWarning);
- return builder.build();
+ return newBuilder()
+ .addAllResults(toCategories(componentsPerQualifiers, recentlyBrowsedKeys, favoriteUuids, componentsByUuids, organizationsByUuids, projectsByUuids))
+ .addAllOrganizations(toOrganizations(organizationsByUuids))
+ .addAllProjects(toProjects(projectsByUuids));
}
- private static List<Category> toCategories(ComponentIndexResults componentsPerQualifiers, Set<String> recentlyBrowsedKeys, Set<String> favoriteKeys,
+ 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) {
return componentsPerQualifiers.getQualifiers().map(qualifier -> {
List<Suggestion> suggestions = qualifier.getHits().stream()
- .map(hit -> toSuggestion(hit, recentlyBrowsedKeys, favoriteKeys, componentsByUuids, organizationByUuids, projectsByUuids))
+ .map(hit -> toSuggestion(hit, recentlyBrowsedKeys, favoriteUuids, componentsByUuids, organizationByUuids, projectsByUuids))
.collect(toList());
return Category.newBuilder()
}).collect(toList());
}
- private static Suggestion toSuggestion(ComponentHit hit, Set<String> recentlyBrowsedKeys, Set<String> favoriteKeys, Map<String, ComponentDto> componentsByUuids,
+ private static Suggestion toSuggestion(ComponentHit hit, Set<String> recentlyBrowsedKeys, Set<String> favoriteUuids, Map<String, ComponentDto> componentsByUuids,
Map<String, OrganizationDto> organizationByUuids, Map<String, ComponentDto> projectsByUuids) {
ComponentDto result = componentsByUuids.get(hit.getUuid());
String organizationKey = organizationByUuids.get(result.getOrganizationUuid()).getKey();
.setName(result.longName())
.setMatch(hit.getHighlightedText().orElse(HtmlEscapers.htmlEscaper().escape(result.longName())))
.setIsRecentlyBrowsed(recentlyBrowsedKeys.contains(result.getKey()))
- .setIsFavorite(favoriteKeys.contains(result.getKey()))
+ .setIsFavorite(favoriteUuids.contains(result.uuid()))
.build();
}
import org.junit.Test;
import org.sonar.api.config.MapSettings;
import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
import org.sonarqube.ws.WsComponents.SuggestionsWsResponse.Project;
import org.sonarqube.ws.WsComponents.SuggestionsWsResponse.Suggestion;
+import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.joining;
import static org.assertj.core.groups.Tuple.tuple;
import static org.mockito.Mockito.doReturn;
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;
private ComponentIndexer componentIndexer = new ComponentIndexer(db.getDbClient(), es.client());
private FavoriteFinder favoriteFinder = mock(FavoriteFinder.class);
private ComponentIndex index = new ComponentIndex(es.client(), new AuthorizationTypeSupport(userSessionRule));
- private SuggestionsAction underTest = new SuggestionsAction(db.getDbClient(), index, favoriteFinder);
+ private SuggestionsAction underTest = new SuggestionsAction(db.getDbClient(), index, favoriteFinder, userSessionRule);
private OrganizationDto organization;
private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, componentIndexer);
- private WsActionTester actionTester = new WsActionTester(underTest);
+ private WsActionTester ws = new WsActionTester(underTest);
@Before
public void setUp() {
@Test
public void define_suggestions_action() {
- WebService.Action action = actionTester.getDef();
+ WebService.Action action = ws.getDef();
assertThat(action).isNotNull();
assertThat(action.isInternal()).isTrue();
assertThat(action.isPost()).isFalse();
PARAM_MORE,
PARAM_QUERY,
PARAM_RECENTLY_BROWSED);
+ assertThat(action.changelog()).extracting(Change::getVersion, Change::getDescription).containsExactlyInAnyOrder(
+ tuple("6.4", "Parameter 's' is optional"));
WebService.Param recentlyBrowsed = action.param(PARAM_RECENTLY_BROWSED);
assertThat(recentlyBrowsed.since()).isEqualTo("6.4");
assertThat(recentlyBrowsed.exampleValue()).isNotEmpty();
assertThat(recentlyBrowsed.description()).isNotEmpty();
assertThat(recentlyBrowsed.isRequired()).isFalse();
+
+ WebService.Param query = action.param(PARAM_QUERY);
+ assertThat(query.exampleValue()).isNotEmpty();
+ assertThat(query.description()).isNotEmpty();
+ assertThat(query.isRequired()).isFalse();
+ }
+
+ @Test
+ public void suggestions_without_query_should_contain_recently_browsed() throws Exception {
+ ComponentDto project = db.components().insertComponent(newPrivateProjectDto(organization));
+
+ componentIndexer.indexOnStartup(null);
+ userSessionRule.addProjectPermission(USER, project);
+
+ SuggestionsWsResponse response = ws.newRequest()
+ .setMethod("POST")
+ .setParam(PARAM_RECENTLY_BROWSED, project.getKey())
+ .executeProtobuf(SuggestionsWsResponse.class);
+
+ // assert match in qualifier "TRK"
+ assertThat(response.getResultsList())
+ .filteredOn(q -> q.getItemsCount() > 0)
+ .extracting(Category::getQ)
+ .containsExactly(Qualifiers.PROJECT);
+
+ // assert correct id to be found
+ assertThat(response.getResultsList())
+ .flatExtracting(Category::getItemsList)
+ .extracting(Suggestion::getKey, Suggestion::getIsRecentlyBrowsed)
+ .containsExactly(tuple(project.getKey(), true));
+ }
+
+ @Test
+ public void suggestions_without_query_should_not_contain_recently_browsed_without_permission() throws Exception {
+ ComponentDto project = db.components().insertComponent(newPrivateProjectDto(organization));
+
+ componentIndexer.indexOnStartup(null);
+
+ SuggestionsWsResponse response = ws.newRequest()
+ .setMethod("POST")
+ .setParam(PARAM_RECENTLY_BROWSED, project.getKey())
+ .executeProtobuf(SuggestionsWsResponse.class);
+
+ assertThat(response.getResultsList())
+ .flatExtracting(Category::getItemsList)
+ .isEmpty();
+ }
+
+ @Test
+ public void suggestions_without_query_should_contain_favorites() throws Exception {
+ ComponentDto project = db.components().insertComponent(newPrivateProjectDto(organization));
+ doReturn(singletonList(project)).when(favoriteFinder).list();
+
+ componentIndexer.indexOnStartup(null);
+ userSessionRule.addProjectPermission(USER, project);
+
+ SuggestionsWsResponse response = ws.newRequest()
+ .setMethod("POST")
+ .executeProtobuf(SuggestionsWsResponse.class);
+
+ // assert match in qualifier "TRK"
+ assertThat(response.getResultsList())
+ .filteredOn(q -> q.getItemsCount() > 0)
+ .extracting(Category::getQ)
+ .containsExactly(Qualifiers.PROJECT);
+
+ // assert correct id to be found
+ assertThat(response.getResultsList())
+ .flatExtracting(Category::getItemsList)
+ .extracting(Suggestion::getKey, Suggestion::getIsFavorite)
+ .containsExactly(tuple(project.getKey(), true));
+ }
+
+ @Test
+ public void suggestions_without_query_should_not_contain_favorites_without_permission() throws Exception {
+ ComponentDto project = db.components().insertComponent(newPrivateProjectDto(organization));
+ doReturn(singletonList(project)).when(favoriteFinder).list();
+
+ componentIndexer.indexOnStartup(null);
+
+ SuggestionsWsResponse response = ws.newRequest()
+ .setMethod("POST")
+ .executeProtobuf(SuggestionsWsResponse.class);
+
+ assertThat(response.getResultsList())
+ .flatExtracting(Category::getItemsList)
+ .isEmpty();
+ }
+
+ @Test
+ public void suggestions_without_query_should_contain_recently_browsed_favorites() throws Exception {
+ ComponentDto project = db.components().insertComponent(newPrivateProjectDto(organization));
+ doReturn(singletonList(project)).when(favoriteFinder).list();
+
+ componentIndexer.indexOnStartup(null);
+ userSessionRule.addProjectPermission(USER, project);
+
+ SuggestionsWsResponse response = ws.newRequest()
+ .setMethod("POST")
+ .setParam(PARAM_RECENTLY_BROWSED, project.key())
+ .executeProtobuf(SuggestionsWsResponse.class);
+
+ // assert match in qualifier "TRK"
+ assertThat(response.getResultsList())
+ .filteredOn(q -> q.getItemsCount() > 0)
+ .extracting(Category::getQ)
+ .containsExactly(Qualifiers.PROJECT);
+
+ // assert correct id to be found
+ assertThat(response.getResultsList())
+ .flatExtracting(Category::getItemsList)
+ .extracting(Suggestion::getKey, Suggestion::getIsFavorite, Suggestion::getIsRecentlyBrowsed)
+ .containsExactly(tuple(project.getKey(), true, true));
+ }
+
+ @Test
+ public void suggestions_without_query_should_not_contain_matches_that_are_neither_favorites_nor_recently_browsed() throws Exception {
+ ComponentDto project = db.components().insertComponent(newPrivateProjectDto(organization));
+
+ componentIndexer.indexOnStartup(null);
+ userSessionRule.addProjectPermission(USER, project);
+
+ SuggestionsWsResponse response = ws.newRequest()
+ .setMethod("POST")
+ .executeProtobuf(SuggestionsWsResponse.class);
+
+ // assert match in qualifier "TRK"
+ assertThat(response.getResultsList())
+ .filteredOn(q -> q.getItemsCount() > 0)
+ .extracting(Category::getQ)
+ .isEmpty();
+ }
+
+ @Test
+ public void suggestions_without_query_should_order_results() throws Exception {
+ ComponentDto project1 = db.components().insertComponent(newPrivateProjectDto(organization).setName("Alpha").setLongName("Alpha"));
+ ComponentDto project2 = db.components().insertComponent(newPrivateProjectDto(organization).setName("Bravo").setLongName("Bravo"));
+ ComponentDto project3 = db.components().insertComponent(newPrivateProjectDto(organization).setName("Charlie").setLongName("Charlie"));
+ ComponentDto project4 = db.components().insertComponent(newPrivateProjectDto(organization).setName("Delta").setLongName("Delta"));
+ doReturn(asList(project4, project2)).when(favoriteFinder).list();
+
+ componentIndexer.indexOnStartup(null);
+ userSessionRule.addProjectPermission(USER, project1);
+ userSessionRule.addProjectPermission(USER, project2);
+ userSessionRule.addProjectPermission(USER, project3);
+ userSessionRule.addProjectPermission(USER, project4);
+
+ SuggestionsWsResponse response = ws.newRequest()
+ .setMethod("POST")
+ .setParam(PARAM_RECENTLY_BROWSED, Stream.of(project3, project1).map(ComponentDto::getKey).collect(joining(",")))
+ .executeProtobuf(SuggestionsWsResponse.class);
+
+ // assert order of keys
+ assertThat(response.getResultsList())
+ .flatExtracting(Category::getItemsList)
+ .extracting(Suggestion::getName, Suggestion::getIsFavorite, Suggestion::getIsRecentlyBrowsed)
+ .containsExactly(
+ tuple("Bravo", true, false),
+ tuple("Delta", true, false),
+ tuple("Alpha", false, true),
+ tuple("Charlie", false, true)
+ );
+ }
+
+ @Test
+ public void suggestions_without_query_should_return_empty_qualifiers() throws Exception {
+ ComponentDto project = db.components().insertComponent(newPrivateProjectDto(organization));
+ componentIndexer.indexProject(project.projectUuid(), ProjectIndexer.Cause.PROJECT_CREATION);
+ userSessionRule.addProjectPermission(USER, project);
+
+ SuggestionsWsResponse response = ws.newRequest()
+ .setMethod("POST")
+ .setParam(PARAM_RECENTLY_BROWSED, project.key())
+ .executeProtobuf(SuggestionsWsResponse.class);
+
+ assertThat(response.getResultsList())
+ .extracting(Category::getQ, Category::getItemsCount)
+ .containsExactlyInAnyOrder(tuple("VW", 0), tuple("SVW", 0), tuple("TRK", 1), tuple("BRC", 0), tuple("FIL", 0), tuple("UTS", 0));
}
@Test
componentIndexer.indexOnStartup(null);
authorizationIndexerTester.allowOnlyAnyone(project);
- SuggestionsWsResponse response = actionTester.newRequest()
+ SuggestionsWsResponse response = ws.newRequest()
.setMethod("POST")
.setParam(PARAM_QUERY, project.getKey())
.executeProtobuf(SuggestionsWsResponse.class);
componentIndexer.indexOnStartup(null);
authorizationIndexerTester.allowOnlyAnyone(project);
- SuggestionsWsResponse response = actionTester.newRequest()
+ SuggestionsWsResponse response = ws.newRequest()
.setMethod("POST")
.setParam(PARAM_QUERY, "S o")
.executeProtobuf(SuggestionsWsResponse.class);
@Test
public void should_warn_about_short_inputs() throws Exception {
- SuggestionsWsResponse response = actionTester.newRequest()
+ SuggestionsWsResponse response = ws.newRequest()
.setMethod("POST")
.setParam(PARAM_QUERY, "validLongToken x")
.executeProtobuf(SuggestionsWsResponse.class);
componentIndexer.indexProject(project2.projectUuid(), ProjectIndexer.Cause.PROJECT_CREATION);
authorizationIndexerTester.allowOnlyAnyone(project2);
- SuggestionsWsResponse response = actionTester.newRequest()
+ SuggestionsWsResponse response = ws.newRequest()
.setMethod("POST")
.setParam(PARAM_QUERY, "Project")
.executeProtobuf(SuggestionsWsResponse.class);
componentIndexer.indexProject(project.projectUuid(), ProjectIndexer.Cause.PROJECT_CREATION);
authorizationIndexerTester.allowOnlyAnyone(project);
- SuggestionsWsResponse response = actionTester.newRequest()
+ SuggestionsWsResponse response = ws.newRequest()
.setMethod("POST")
.setParam(PARAM_QUERY, "Module")
.executeProtobuf(SuggestionsWsResponse.class);
componentIndexer.indexProject(project.projectUuid(), ProjectIndexer.Cause.PROJECT_CREATION);
authorizationIndexerTester.allowOnlyAnyone(project);
- SuggestionsWsResponse response = actionTester.newRequest()
+ SuggestionsWsResponse response = ws.newRequest()
.setMethod("POST")
.setParam(PARAM_QUERY, "Module")
.setParam(PARAM_RECENTLY_BROWSED, Stream.of(module1.getKey()).collect(joining(",")))
componentIndexer.indexProject(project.projectUuid(), ProjectIndexer.Cause.PROJECT_CREATION);
authorizationIndexerTester.allowOnlyAnyone(project);
- SuggestionsWsResponse response = actionTester.newRequest()
+ SuggestionsWsResponse response = ws.newRequest()
.setMethod("POST")
.setParam(PARAM_QUERY, "Module")
.executeProtobuf(SuggestionsWsResponse.class);
.containsExactly(tuple(favorite.getKey(), true), tuple(nonFavorite.getKey(), false));
}
+ @Test
+ public void should_return_empty_qualifiers() throws Exception {
+ ComponentDto project = db.components().insertComponent(newPrivateProjectDto(organization));
+ componentIndexer.indexProject(project.projectUuid(), ProjectIndexer.Cause.PROJECT_CREATION);
+ authorizationIndexerTester.allowOnlyAnyone(project);
+
+ SuggestionsWsResponse response = ws.newRequest()
+ .setMethod("POST")
+ .setParam(PARAM_QUERY, project.name())
+ .executeProtobuf(SuggestionsWsResponse.class);
+
+ assertThat(response.getResultsList())
+ .extracting(Category::getQ, Category::getItemsCount)
+ .containsExactlyInAnyOrder(tuple("VW", 0), tuple("SVW", 0), tuple("TRK", 1), tuple("BRC", 0), tuple("FIL", 0), tuple("UTS", 0));
+ }
+
@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);
componentIndexer.indexOnStartup(null);
projects.forEach(authorizationIndexerTester::allowOnlyAnyone);
- TestRequest request = actionTester.newRequest()
+ TestRequest request = ws.newRequest()
.setMethod("POST")
.setParam(PARAM_QUERY, namePrefix);
ofNullable(more).ifPresent(c -> request.setParam(PARAM_MORE, c.getName()));