diff options
author | Daniel Schwarz <daniel.schwarz@sonarsource.com> | 2017-01-12 12:38:19 +0100 |
---|---|---|
committer | Daniel Schwarz <daniel.schwarz@sonarsource.com> | 2017-01-16 17:42:59 +0100 |
commit | 377c284feae0459ea81eb867a20d4de12068eedd (patch) | |
tree | 4c5a7079e6b86373d83535c21678351fd7371c9e | |
parent | 5a4c43a4b1c4d41211c141e2682c813d06c280ab (diff) | |
download | sonarqube-377c284feae0459ea81eb867a20d4de12068eedd.tar.gz sonarqube-377c284feae0459ea81eb867a20d4de12068eedd.zip |
SONAR-7282 improve relevancy of results
Changes regarding index definition, code style, tests.
11 files changed, 286 insertions, 138 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java index 64ec07cb2bf..0be403673bb 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java @@ -142,7 +142,7 @@ public class ComponentService { private void index(String projectUuid) { projectMeasuresIndexer.index(projectUuid); - componentIndexer.index(projectUuid); + componentIndexer.indexByProjectUuid(projectUuid); } /** diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java index 360a5fa226d..e60bf8136d2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java @@ -21,12 +21,14 @@ package org.sonar.server.component.index; import java.util.Arrays; import java.util.List; +import org.apache.commons.lang.StringUtils; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.search.SearchHit; import org.sonar.core.util.stream.Collectors; import org.sonar.server.es.BaseIndex; +import org.sonar.server.es.DefaultIndexSettings; import org.sonar.server.es.EsClient; import static org.elasticsearch.index.query.QueryBuilders.boolQuery; @@ -45,33 +47,30 @@ public class ComponentIndex extends BaseIndex { } public List<String> search(ComponentIndexQuery query) { - SearchRequestBuilder requestBuilder = getClient() + SearchRequestBuilder request = getClient() .prepareSearch(INDEX_COMPONENTS) .setTypes(TYPE_COMPONENT) .setFetchSource(false); - query.getLimit().ifPresent(requestBuilder::setSize); + query.getLimit().ifPresent(request::setSize); - requestBuilder.setQuery( - createQuery(query)); + request.setQuery(createQuery(query)); - return Arrays.stream(requestBuilder.get().getHits().hits()) + return Arrays.stream(request.get().getHits().hits()) .map(SearchHit::getId) .collect(Collectors.toList()); } private static QueryBuilder createQuery(ComponentIndexQuery query) { BoolQueryBuilder esQuery = boolQuery(); + query.getQualifier().ifPresent(q -> esQuery.filter(termQuery(FIELD_QUALIFIER, q))); - query - .getQualifiers() - .forEach(qualifier -> esQuery.filter(termQuery(FIELD_QUALIFIER, qualifier))); + // We will truncate the search to the maximum length of nGrams in the index. + // Otherwise the search would for sure not find any results. + String truncatedQuery = StringUtils.left(query.getQuery(), DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH); return esQuery - .must( - boolQuery() - .should(matchQuery(FIELD_NAME + "." + SEARCH_PARTIAL_SUFFIX, query.getQuery())) - .should(termQuery(FIELD_KEY, query.getQuery()).boost(3f)) - .minimumNumberShouldMatch(1)); + .should(matchQuery(FIELD_NAME + "." + SEARCH_PARTIAL_SUFFIX, truncatedQuery)) + .should(matchQuery(FIELD_KEY + "." + SORT_SUFFIX, query.getQuery()).boost(3f)); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexDefinition.java b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexDefinition.java index ed2d22cdcf4..4c0b229ec2f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexDefinition.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexDefinition.java @@ -50,7 +50,7 @@ public class ComponentIndexDefinition implements IndexDefinition { // type "component" NewIndex.NewIndexType mapping = index.createType(TYPE_COMPONENT); mapping.stringFieldBuilder(FIELD_PROJECT_UUID).build(); - mapping.stringFieldBuilder(FIELD_KEY).build(); + mapping.stringFieldBuilder(FIELD_KEY).enableSorting().build(); mapping.stringFieldBuilder(FIELD_NAME).enableGramSearch().build(); mapping.stringFieldBuilder(FIELD_QUALIFIER).build(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexQuery.java b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexQuery.java index f700206a41d..da85a3b0e41 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexQuery.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexQuery.java @@ -19,45 +19,42 @@ */ package org.sonar.server.component.index; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.Optional; -import java.util.Set; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; public class ComponentIndexQuery { private final String query; - private Set<String> qualifiers = new HashSet<>(); + private Optional<String> qualifier = Optional.empty(); private Optional<Integer> limit = Optional.empty(); public ComponentIndexQuery(String query) { + requireNonNull(query); + checkArgument(query.length() >= 2, "Query must be at least two characters long: %s", query); this.query = query; } - public ComponentIndexQuery addQualifier(String qualifier) { - this.qualifiers.add(qualifier); - return this; - } - - public ComponentIndexQuery addQualifiers(Collection<String> qualifiers) { - this.qualifiers.addAll(qualifiers); + public ComponentIndexQuery setQualifier(@Nullable String qualifier) { + this.qualifier = Optional.ofNullable(qualifier); return this; } - public boolean hasQualifiers() { - return !qualifiers.isEmpty(); - } - - public Collection<String> getQualifiers() { - return Collections.unmodifiableSet(qualifiers); + public Optional<String> getQualifier() { + return qualifier; } public String getQuery() { return query; } + /** + * The number of search hits to return per Qualifier. Defaults to <tt>10</tt>. + */ public ComponentIndexQuery setLimit(int limit) { + checkArgument(limit >= 1, "Limit has to be strictly positive: %s", limit); this.limit = Optional.of(limit); return this; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexer.java b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexer.java index 9c147fc268a..386711534bf 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexer.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexer.java @@ -36,6 +36,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.server.es.BulkIndexer; import org.sonar.server.es.EsClient; +import static org.elasticsearch.index.query.QueryBuilders.boolQuery; import static org.elasticsearch.index.query.QueryBuilders.termQuery; import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_PROJECT_UUID; import static org.sonar.server.component.index.ComponentIndexDefinition.INDEX_COMPONENTS; @@ -56,23 +57,28 @@ public class ComponentIndexer implements Startable { /** * Copy all components of all projects to the elastic search index. * <p> - * <b>Warning</b>: This should only be called on an empty index. It does not purge anything. + * <b>Warning</b>: This should only be called on an empty index. It does not delete anything. */ public void index() { try (DbSession dbSession = dbClient.openSession(false)) { + BulkIndexer bulk = new BulkIndexer(esClient, INDEX_COMPONENTS); + bulk.setLarge(true); + bulk.start(); dbClient.componentDao() - .selectProjects(dbSession) - .stream() - .forEach(this::index); + .selectAll(dbSession, context -> { + ComponentDto dto = (ComponentDto) context.getResultObject(); + bulk.add(newIndexRequest(toDocument(dto))); + }); + bulk.stop(); } } /** - * Update the elastic search for one specific project. The current data from the database is used. + * Update the index for one specific project. The current data from the database is used. */ - public void index(String projectUuid) { + public void indexByProjectUuid(String projectUuid) { try (DbSession dbSession = dbClient.openSession(false)) { - purge(projectUuid); + deleteByProjectUuid(projectUuid); index( dbClient .componentDao() @@ -81,12 +87,12 @@ public class ComponentIndexer implements Startable { } } - private void purge(String projectUuid) { + private void deleteByProjectUuid(String projectUuid) { BulkIndexer.delete(esClient, INDEX_COMPONENTS, esClient .prepareSearch(INDEX_COMPONENTS) .setTypes(TYPE_COMPONENT) .setFetchSource(false) - .setQuery(termQuery(FIELD_PROJECT_UUID, projectUuid))); + .setQuery(boolQuery().filter(termQuery(FIELD_PROJECT_UUID, projectUuid)))); } void index(ComponentDto... docs) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java index 29e507a7fcb..30be47d01a9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java @@ -44,7 +44,7 @@ import static org.sonarqube.ws.client.component.ComponentsWsParameters.ACTION_SU public class SuggestionsAction implements ComponentsWsAction { - static final String URL_PARAM_QUERY = "s"; + private static final String URL_PARAM_QUERY = "s"; private static final String[] QUALIFIERS = { Qualifiers.VIEW, @@ -104,7 +104,7 @@ public class SuggestionsAction implements ComponentsWsAction { private Optional<Qualifier> getResultsOfQualifier(String query, String qualifier) { ComponentIndexQuery componentIndexQuery = new ComponentIndexQuery(query) - .addQualifier(qualifier) + .setQualifier(qualifier) .setLimit(NUMBER_OF_RESULTS_PER_QUALIFIER); List<String> uuids = searchInIndex(componentIndexQuery); @@ -128,7 +128,7 @@ public class SuggestionsAction implements ComponentsWsAction { return index.search(componentIndexQuery); } - public List<ComponentDto> fetchFromDatabase(List<String> uuids) { + private List<ComponentDto> fetchFromDatabase(List<String> uuids) { DbSession dbSession = dbClient.openSession(false); try { return dbClient.componentDao().selectByUuids(dbSession, uuids); diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/IndexComponentsStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/IndexComponentsStep.java index 058dc894797..40f38b6ce73 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/IndexComponentsStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/IndexComponentsStep.java @@ -40,7 +40,7 @@ public class IndexComponentsStep implements ComputationStep { public void execute() { String projectUuid = treeRootHolder.getRoot().getUuid(); resourceIndexDao.indexProject(projectUuid); - elasticSearchIndexer.index(projectUuid); + elasticSearchIndexer.indexByProjectUuid(projectUuid); } @Override diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/DefaultIndexSettings.java b/server/sonar-server/src/main/java/org/sonar/server/es/DefaultIndexSettings.java index 3fe2311efe6..67344cce005 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/es/DefaultIndexSettings.java +++ b/server/sonar-server/src/main/java/org/sonar/server/es/DefaultIndexSettings.java @@ -22,7 +22,10 @@ package org.sonar.server.es; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.settings.Settings; -class DefaultIndexSettings { +public class DefaultIndexSettings { + + /** Maximum length of ngrams. */ + public static final int MAXIMUM_NGRAM_LENGTH = 15; private DefaultIndexSettings() { // only static stuff @@ -72,7 +75,7 @@ class DefaultIndexSettings { // Edge NGram filter .put("index.analysis.filter.gram_filter.type", "nGram") .put("index.analysis.filter.gram_filter.min_gram", 2) - .put("index.analysis.filter.gram_filter.max_gram", 15) + .put("index.analysis.filter.gram_filter.max_gram", MAXIMUM_NGRAM_LENGTH) .putArray("index.analysis.filter.gram_filter.token_chars", "letter", "digit", "punctuation", "symbol") // Word filter diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexQueryTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexQueryTest.java new file mode 100644 index 00000000000..3fd67b6df54 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexQueryTest.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ +package org.sonar.server.component.index; + +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ComponentIndexQueryTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void should_fail_with_IAE_if_query_is_empty() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Query must be at least two characters long"); + + new ComponentIndexQuery(""); + } + + @Test + public void should_fail_with_IAE_if_query_is_one_character_long() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Query must be at least two characters long"); + + new ComponentIndexQuery("a"); + } + + @Test + public void should_support_query_with_two_characters_long() { + ComponentIndexQuery query = new ComponentIndexQuery("ab"); + + assertThat(query.getQuery()).isEqualTo("ab"); + } + + @Test + public void should_fail_with_IAE_if_limit_is_negative() { + ComponentIndexQuery query = new ComponentIndexQuery("ab"); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Limit has to be strictly positive"); + + query.setLimit(-1); + } + + @Test + public void should_fail_with_IAE_if_limit_is_zero() { + ComponentIndexQuery query = new ComponentIndexQuery("ab"); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Limit has to be strictly positive"); + + query.setLimit(0); + } + + @Test + public void should_support_positive_limit() { + ComponentIndexQuery query = new ComponentIndexQuery("ab") + .setLimit(1); + + assertThat(query.getLimit()).isEqualTo(Optional.of(1)); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java index 4ed8479ea64..c9bfe62b3e2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java @@ -19,10 +19,10 @@ */ package org.sonar.server.component.index; -import java.util.Collection; +import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.assertj.core.api.AbstractListAssert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -36,23 +36,10 @@ import org.sonar.db.organization.OrganizationDto; import org.sonar.db.organization.OrganizationTesting; import org.sonar.server.es.EsTester; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; public class ComponentIndexTest { - private static final String BLA = "bla"; - private static final String UUID_DOC = "UUID-DOC-"; - private static final String UUID_DOC_1 = UUID_DOC + "1"; - private static final String KEY = "KEY-"; - private static final String KEY_1 = KEY + "1"; - - private static final String PREFIX = "Son"; - private static final String MIDDLE = "arQ"; - private static final String SUFFIX = "ube"; - private static final String PREFIX_MIDDLE_SUFFIX = PREFIX + MIDDLE + SUFFIX; - @Rule public EsTester es = new EsTester(new ComponentIndexDefinition(new MapSettings())); @@ -71,133 +58,205 @@ public class ComponentIndexTest { } @Test - public void empty_search() { - assertSearch(emptyList(), BLA, emptyList()); + public void return_empty_list_if_no_fields_match_query() { + indexProject("struts", "Apache Struts"); + + assertThat(index.search(new ComponentIndexQuery("missing"))).isEmpty(); } @Test - public void exact_match_search() { - assertMatch(BLA, BLA); + public void search_projects_by_exact_name() { + ComponentDto struts = indexProject("struts", "Apache Struts"); + indexProject("sonarqube", "SonarQube"); + + assertSearchResults("Apache Struts", struts); + assertSearchResults("APACHE STRUTS", struts); + assertSearchResults("APACHE struTS", struts); } @Test - public void ignore_case() { - assertMatch("bLa", "BlA"); + public void search_file_with_long_name() { + ComponentDto project = indexProject("struts", "Apache Struts"); + ComponentDto file1 = indexFile(project, "src/main/java/DefaultRubyComponentServiceTestManagerFactory.java", "DefaultRubyComponentServiceTestManagerFactory.java"); + + assertSearchResults("DefaultRubyComponentServiceTestManagerFactory", file1); + assertSearchResults("DefaultRubyComponentServiceTestManagerFactory.java", file1); + assertSearchResults("RubyComponentServiceTestManager", file1); + assertSearchResults("te", file1); } @Test - public void search_for_different_qualifier() { + public void should_search_by_name_with_two_characters() { + ComponentDto project = indexProject("struts", "Apache Struts"); - // create a component of type project - ComponentDto project = ComponentTesting - .newProjectDto(organization, UUID_DOC_1) - .setName(BLA) - .setKey(BLA); + assertSearchResults("st", project); + assertSearchResults("tr", project); + } - // search for components of type file - ComponentIndexQuery fileQuery = new ComponentIndexQuery(BLA); - fileQuery.addQualifier(Qualifiers.FILE); + @Test + public void search_projects_by_partial_name() { + ComponentDto struts = indexProject("struts", "Apache Struts"); - // should not have any results - assertThat(search(asList(project), fileQuery)).isEmpty(); + assertSearchResults("truts", struts); + assertSearchResults("pache", struts); + assertSearchResults("apach", struts); + assertSearchResults("che stru", struts); } @Test - public void prefix_match_search() { - assertMatch(PREFIX_MIDDLE_SUFFIX, PREFIX); + public void search_projects_and_files_by_partial_name() { + ComponentDto project = indexProject("struts", "Apache Struts"); + ComponentDto file1 = indexFile(project, "src/main/java/StrutsManager.java", "StrutsManager.java"); + indexFile(project, "src/main/java/Foo.java", "Foo.java"); + + assertSearchResults("struts", project, file1); + assertSearchResults("Struts", project, file1); + assertSearchResults("StrutsManager", file1); + assertSearchResults("STRUTSMA", file1); + assertSearchResults("utsManag", file1); } @Test - public void middle_match_search() { - assertMatch(PREFIX_MIDDLE_SUFFIX, MIDDLE); + public void should_find_file_by_file_extension() { + ComponentDto project = indexProject("struts", "Apache Struts"); + ComponentDto file1 = indexFile(project, "src/main/java/StrutsManager.java", "StrutsManager.java"); + ComponentDto file2 = indexFile(project, "src/main/java/Foo.java", "Foo.java"); + + assertSearchResults(".java", file1, file2); + assertSearchResults("manager.java", file1); + + // do not match + assertNoSearchResults("strutsmanager.txt"); + assertNoSearchResults("strutsmanagerjava"); + assertNoSearchResults("somethingStrutsManager.java"); } @Test - public void suffix_match_search() { - assertMatch(PREFIX_MIDDLE_SUFFIX, SUFFIX); + public void should_search_projects_by_exact_case_insensitive_key() { + ComponentDto project1 = indexProject("keyOne", "Project One"); + indexProject("keyTwo", "Project Two"); + + assertSearchResults("keyOne", project1); + assertSearchResults("keyone", project1); + assertSearchResults("KEYone", project1); } @Test - public void exact_match_should_be_shown_first() { - ComponentDto good = newDoc(UUID_DOC + 1, "Current SonarQube Plattform"); - ComponentDto better = newDoc(UUID_DOC + 2, "SonarQube Plattform"); + public void should_search_project_with_dot_in_key() { + ComponentDto project = indexProject("org.sonarqube", "SonarQube"); - assertThat(search(asList(good, better), "SonarQube")) - .containsExactly(better.uuid(), good.uuid()); + assertSearchResults("org.sonarqube", project); + assertNoSearchResults("orgsonarqube"); } @Test - public void do_not_interpret_input() { - assertNotMatch(BLA, "*"); + public void should_search_project_with_dash_in_key() { + ComponentDto project = indexProject("org-sonarqube", "SonarQube"); + + assertSearchResults("org-sonarqube", project); + assertNoSearchResults("orgsonarqube"); } @Test - public void key_match_search() { - assertSearch( - asList(newDoc(UUID_DOC_1, "name is not a match", "matchingKey")), - "matchingKey", - asList(UUID_DOC_1)); + public void should_search_project_with_colon_in_key() { + ComponentDto project = indexProject("org:sonarqube", "SonarQube"); + + assertSearchResults("org:sonarqube", project); + assertNoSearchResults("orgsonarqube"); + assertNoSearchResults("org-sonarqube"); + assertNoSearchResults("org_sonarqube"); } @Test - public void unmatching_search() { - assertNotMatch(BLA, "blubb"); + public void should_search_project_with_all_special_characters_in_key() { + ComponentDto project = indexProject("org.sonarqube:sonar-sérvèr_ç", "SonarQube"); + + assertSearchResults("org.sonarqube:sonar-sérvèr_ç", project); } @Test - public void limit_number_of_documents() { - Collection<ComponentDto> docs = IntStream - .rangeClosed(1, 42) - .mapToObj(i -> newDoc(UUID_DOC + i, BLA, KEY + i)) - .collect(Collectors.toList()); + public void should_not_return_results_when_searching_by_partial_key() { + indexProject("theKey", "the name"); - int pageSize = 41; - ComponentIndexQuery componentIndexQuery = new ComponentIndexQuery(BLA) - .addQualifier(Qualifiers.PROJECT) - .setLimit(pageSize); - assertThat(search(docs, componentIndexQuery)).hasSize(pageSize); + assertNoSearchResults("theke"); + assertNoSearchResults("hekey"); } - private void assertMatch(String name, String query) { - assertSearch( - asList(newDoc(name)), - query, - asList(UUID_DOC_1)); + @Test + public void filter_results_by_qualifier() { + ComponentDto project = indexProject("struts", "Apache Struts"); + indexFile(project, "src/main/java/StrutsManager.java", "StrutsManager.java"); + + assertSearchResults(new ComponentIndexQuery("struts").setQualifier(Qualifiers.PROJECT), project); } - private void assertNotMatch(String name, String query) { - assertSearch( - asList(newDoc(name)), - query, - emptyList()); + @Test + public void should_order_results_by_score() { + ComponentDto project1 = indexProject("keyOne", "Struts"); + ComponentDto project2 = indexProject("keyTwo", "Apache Struts Two"); + ComponentDto project3 = indexProject("keyThree", "Apache Struts"); + + assertSearch("struts").containsExactly(project1.uuid(), project3.uuid(), project2.uuid()); } - private ComponentDto newDoc(String name) { - return newDoc(UUID_DOC_1, name); + @Test + public void should_prefer_key_matching_over_name_matching() { + ComponentDto project1 = indexProject("quality", "SonarQube"); + ComponentDto project2 = indexProject("sonarqube", "Quality Product"); + + assertSearch("sonarqube").containsExactly(project2.uuid(), project1.uuid()); + } + + @Test + public void should_limit_the_number_of_results() { + IntStream.rangeClosed(0, 10).forEach(i -> indexProject("sonarqube" + i, "SonarQube" + i)); + + assertSearch(new ComponentIndexQuery("sonarqube").setLimit(5)).hasSize(5); + } + + @Test + public void should_not_support_wildcards() { + indexProject("theKey", "the name"); + + assertNoSearchResults("*the*"); + assertNoSearchResults("th?Key"); + } + + private AbstractListAssert<?, ? extends List<? extends String>, String> assertSearch(String query) { + return assertSearch(new ComponentIndexQuery(query)); } - private ComponentDto newDoc(String uuid, String name) { - return newDoc(uuid, name, KEY_1); + private AbstractListAssert<?, ? extends List<? extends String>, String> assertSearch(ComponentIndexQuery query) { + return assertThat(index.search(query)); } - private ComponentDto newDoc(String uuid, String name, String key) { - return ComponentTesting - .newProjectDto(organization, uuid) - .setName(name) - .setKey(key); + private void assertSearchResults(String query, ComponentDto... expectedComponents) { + assertSearchResults(new ComponentIndexQuery(query), expectedComponents); } - private void assertSearch(Collection<ComponentDto> input, String queryText, Collection<String> expectedOutput) { - assertThat(search(input, queryText)) - .hasSameElementsAs(expectedOutput); + private void assertSearchResults(ComponentIndexQuery query, ComponentDto... expectedComponents) { + String[] expectedUuids = Arrays.stream(expectedComponents).map(ComponentDto::uuid).toArray(String[]::new); + assertSearch(query).containsOnly(expectedUuids); } - private List<String> search(Collection<ComponentDto> input, String query) { - return search(input, new ComponentIndexQuery(query).addQualifier(Qualifiers.PROJECT)); + private void assertNoSearchResults(String query) { + assertSearchResults(query); } - private List<String> search(Collection<ComponentDto> input, ComponentIndexQuery query) { - input.stream().forEach(indexer::index); - return index.search(query); + private ComponentDto indexProject(String key, String name) { + ComponentDto dto = ComponentTesting.newProjectDto(organization, "UUID_" + key) + .setKey(key) + .setName(name); + indexer.index(dto); + return dto; } + + private ComponentDto indexFile(ComponentDto project, String fileKey, String fileName) { + ComponentDto dto = ComponentTesting.newFileDto(project) + .setKey(fileKey) + .setName(fileName); + indexer.index(dto); + return dto; + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java index 64e89c164f0..23bea430a5b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java @@ -143,7 +143,7 @@ public class ComponentIndexerTest { } private void index(String uuid) { - createIndexer().index(uuid); + createIndexer().indexByProjectUuid(uuid); } private long count() { |