aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2017-02-21 10:01:15 +0100
committerJulien Lancelot <julien.lancelot@sonarsource.com>2017-02-22 15:33:57 +0100
commit29fa51a5b900a29a5f7384c265d1ffe9c9140455 (patch)
treea38b71d7ad6c39330b222721984aad30965361a8 /server
parentc7c06d7ae381d87a37fb4d9eaeb7fe38ce29de8a (diff)
downloadsonarqube-29fa51a5b900a29a5f7384c265d1ffe9c9140455.tar.gz
sonarqube-29fa51a5b900a29a5f7384c265d1ffe9c9140455.zip
SONAR-8795 Search by text query
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/FilterParser.java21
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java23
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java19
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexDefinition.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java10
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/FilterParserTest.java25
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java37
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java13
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java13
9 files changed, 156 insertions, 10 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/FilterParser.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/FilterParser.java
index caa60c55b5a..f65bfeff006 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/FilterParser.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/FilterParser.java
@@ -37,12 +37,18 @@ import static java.util.Objects.requireNonNull;
public class FilterParser {
+ private static final String DOUBLE_QUOTES = "\"";
+
private static final Splitter CRITERIA_SPLITTER = Splitter.on(Pattern.compile("and", Pattern.CASE_INSENSITIVE));
private static final Splitter IN_VALUES_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
- private static final Pattern PATTERN = Pattern.compile("(\\w+)\\s*([<>]?[=]?)\\s*(\\S*)", Pattern.CASE_INSENSITIVE);
+ private static final Pattern PATTERN = Pattern.compile("(\\w+)\\s*([<>]?[=]?)\\s*(.*)", Pattern.CASE_INSENSITIVE);
private static final Pattern PATTERN_HAVING_VALUES = Pattern.compile("(\\w+)\\s+(in)\\s+\\((.*)\\)", Pattern.CASE_INSENSITIVE);
+ private FilterParser(){
+ // Only static methods
+ }
+
public static List<Criterion> parse(String filter) {
return StreamSupport.stream(CRITERIA_SPLITTER.split(filter.trim()).spliterator(), false)
.filter(Objects::nonNull)
@@ -80,7 +86,7 @@ public class FilterParser {
String value = matcher.group(3);
if (!isNullOrEmpty(operatorValue) && !isNullOrEmpty(value)) {
builder.setOperator(Operator.getByValue(operatorValue));
- builder.setValue(value);
+ builder.setValue(sanitizeValue(value));
}
return builder.build();
}
@@ -98,6 +104,17 @@ public class FilterParser {
return builder.build();
}
+ @CheckForNull
+ private static String sanitizeValue(@Nullable String value) {
+ if (value == null) {
+ return null;
+ }
+ if (value.length() > 2 && value.startsWith(DOUBLE_QUOTES) && value.endsWith(DOUBLE_QUOTES)) {
+ return value.substring(1, value.length() - 1);
+ }
+ return value;
+ }
+
public static class Criterion {
private final String key;
private final Operator operator;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java
index aff52a9f99a..dc2e9330b1e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java
@@ -42,6 +42,7 @@ import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_LANGUA
class ProjectMeasuresQueryFactory {
public static final String IS_FAVORITE_CRITERION = "isFavorite";
+ public static final String QUERY_KEY = "query";
private ProjectMeasuresQueryFactory() {
// prevent instantiation
@@ -67,6 +68,11 @@ class ProjectMeasuresQueryFactory {
return;
}
+ if (QUERY_KEY.equalsIgnoreCase(key)) {
+ processQuery(criterion, query);
+ return;
+ }
+
String value = criterion.getValue();
checkArgument(value != null, "Value cannot be null for '%s'", key);
if (ALERT_STATUS_KEY.equals(key)) {
@@ -82,11 +88,21 @@ class ProjectMeasuresQueryFactory {
List<String> values = criterion.getValues();
if (value != null && EQ.equals(operator)) {
query.setLanguages(singleton(value));
- } else if (!values.isEmpty() && IN.equals(operator)) {
+ return;
+ }
+ if (!values.isEmpty() && IN.equals(operator)) {
query.setLanguages(new HashSet<>(values));
- } else {
- throw new IllegalArgumentException("Language should be set either by using 'language = java' or 'language IN (java, js)'");
+ return;
}
+ throw new IllegalArgumentException("Language should be set either by using 'language = java' or 'language IN (java, js)'");
+ }
+
+ private static void processQuery(FilterParser.Criterion criterion, ProjectMeasuresQuery query) {
+ Operator operatorValue = criterion.getOperator();
+ String value = criterion.getValue();
+ checkArgument(value != null, "Query is invalid");
+ checkArgument(EQ.equals(operatorValue), "Query should only be used with equals operator");
+ query.setQueryText(value);
}
private static void processQualityGateStatus(FilterParser.Criterion criterion, ProjectMeasuresQuery query) {
@@ -106,5 +122,4 @@ class ProjectMeasuresQueryFactory {
}
}
-
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
index 8571ab16f17..8938eabc7d2 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
@@ -26,6 +26,7 @@ import com.google.common.collect.Multimap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.stream.IntStream;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.index.query.BoolQueryBuilder;
@@ -41,6 +42,8 @@ import org.sonar.server.es.EsClient;
import org.sonar.server.es.SearchIdResult;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.es.StickyFacetBuilder;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeature;
+import org.sonar.server.es.textsearch.ComponentTextSearchQueryFactory;
import org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
import org.sonar.server.permission.index.AuthorizationTypeSupport;
@@ -242,7 +245,6 @@ public class ProjectMeasuresIndex extends BaseIndex {
.filter(toValueQuery(criterion))))
.forEach(metricFilters::must);
filters.put(entry.getKey(), metricFilters);
-
});
query.getQualityGateStatus()
@@ -257,9 +259,24 @@ public class ProjectMeasuresIndex extends BaseIndex {
query.getOrganizationUuid()
.ifPresent(organizationUuid -> filters.put(FIELD_ORGANIZATION_UUID, termQuery(FIELD_ORGANIZATION_UUID, organizationUuid)));
+
+ createTextQueryFilter(query).ifPresent(queryBuilder -> filters.put("textQuery", queryBuilder));
return filters;
}
+ private static Optional<QueryBuilder> createTextQueryFilter(ProjectMeasuresQuery query) {
+ Optional<String> queryText = query.getQueryText();
+ if (!queryText.isPresent()) {
+ return Optional.empty();
+ }
+ ComponentTextSearchQueryFactory.ComponentTextSearchQuery componentTextSearchQuery = ComponentTextSearchQueryFactory.ComponentTextSearchQuery.builder()
+ .setQueryText(queryText.get())
+ .setFieldKey(FIELD_KEY)
+ .setFieldName(FIELD_NAME)
+ .build();
+ return Optional.of(ComponentTextSearchQueryFactory.createQuery(componentTextSearchQuery, ComponentTextSearchFeature.values()));
+ }
+
private static QueryBuilder toValueQuery(MetricCriterion criterion) {
String fieldName = FIELD_MEASURES_VALUE;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexDefinition.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexDefinition.java
index 5a3558b127a..9e85ab83a0e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexDefinition.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexDefinition.java
@@ -23,6 +23,7 @@ import org.sonar.api.config.Settings;
import org.sonar.server.es.IndexDefinition;
import org.sonar.server.es.NewIndex;
+import static org.sonar.server.es.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
import static org.sonar.server.es.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
public class ProjectMeasuresIndexDefinition implements IndexDefinition {
@@ -58,8 +59,8 @@ public class ProjectMeasuresIndexDefinition implements IndexDefinition {
.requireProjectAuthorization();
mapping.stringFieldBuilder(FIELD_ORGANIZATION_UUID).build();
- mapping.stringFieldBuilder(FIELD_KEY).disableNorms().build();
- mapping.stringFieldBuilder(FIELD_NAME).addSubFields(SORTABLE_ANALYZER).build();
+ mapping.stringFieldBuilder(FIELD_KEY).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
+ mapping.stringFieldBuilder(FIELD_NAME).addSubFields(SORTABLE_ANALYZER, SEARCH_GRAMS_ANALYZER).build();
mapping.stringFieldBuilder(FIELD_QUALITY_GATE_STATUS).build();
mapping.createDateTimeField(FIELD_ANALYSED_AT);
mapping.nestedFieldBuilder(FIELD_MEASURES)
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java
index 2bc12465beb..0b484c78ce2 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java
@@ -40,6 +40,7 @@ public class ProjectMeasuresQuery {
private Set<String> languages;
private String sort = SORT_BY_NAME;
private boolean asc = true;
+ private String queryText;
public ProjectMeasuresQuery addMetricCriterion(MetricCriterion metricCriterion) {
this.metricCriteria.add(metricCriterion);
@@ -86,6 +87,15 @@ public class ProjectMeasuresQuery {
return Optional.ofNullable(languages);
}
+ public Optional<String> getQueryText() {
+ return Optional.ofNullable(queryText);
+ }
+
+ public ProjectMeasuresQuery setQueryText(@Nullable String queryText) {
+ this.queryText = queryText;
+ return this;
+ }
+
public String getSort() {
return sort;
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/FilterParserTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/FilterParserTest.java
index 09c8e90120f..27a429f8fe4 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/FilterParserTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/FilterParserTest.java
@@ -133,7 +133,7 @@ public class FilterParserTest {
.containsOnly(
tuple("ncloc", GT, "10", emptyList()),
tuple("coverage", LTE, "80", emptyList()),
- tuple("language", IN, null, asList("java", "js")));
+ tuple("language", IN, null, asList("java", "js")));
}
@Test
@@ -152,6 +152,29 @@ public class FilterParserTest {
}
@Test
+ public void parse_filter_starting_and_ending_with_double_quotes() throws Exception {
+ assertThat(FilterParser.parse("q = \"Sonar Qube\""))
+ .extracting(Criterion::getKey, Criterion::getOperator, Criterion::getValue)
+ .containsOnly(
+ tuple("q", EQ, "Sonar Qube"));
+
+ assertThat(FilterParser.parse("q = \"Sonar\"Qube\""))
+ .extracting(Criterion::getKey, Criterion::getOperator, Criterion::getValue)
+ .containsOnly(
+ tuple("q", EQ, "Sonar\"Qube"));
+
+ assertThat(FilterParser.parse("q = Sonar\"Qube"))
+ .extracting(Criterion::getKey, Criterion::getOperator, Criterion::getValue)
+ .containsOnly(
+ tuple("q", EQ, "Sonar\"Qube"));
+
+ assertThat(FilterParser.parse("q=\"Sonar Qube\""))
+ .extracting(Criterion::getKey, Criterion::getOperator, Criterion::getValue)
+ .containsOnly(
+ tuple("q", EQ, "Sonar Qube"));
+ }
+
+ @Test
public void accept_empty_query() throws Exception {
List<Criterion> criterion = FilterParser.parse("");
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java
index 79fd62ac2e6..215949fba57 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java
@@ -39,6 +39,7 @@ import static org.sonar.server.component.ws.FilterParser.Operator;
import static org.sonar.server.component.ws.FilterParser.Operator.EQ;
import static org.sonar.server.component.ws.FilterParser.Operator.GT;
import static org.sonar.server.component.ws.FilterParser.Operator.IN;
+import static org.sonar.server.component.ws.FilterParser.Operator.LT;
import static org.sonar.server.component.ws.FilterParser.Operator.LTE;
import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
import static org.sonar.server.computation.task.projectanalysis.measure.Measure.Level.OK;
@@ -153,6 +154,42 @@ public class ProjectMeasuresQueryFactoryTest {
}
@Test
+ public void create_query_having_q() throws Exception {
+ List<Criterion> criteria = singletonList(Criterion.builder().setKey("query").setOperator(EQ).setValue("Sonar Qube").build());
+
+ ProjectMeasuresQuery underTest = newProjectMeasuresQuery(criteria, emptySet());
+
+ assertThat(underTest.getQueryText().get()).isEqualTo("Sonar Qube");
+ }
+
+ @Test
+ public void create_query_having_q_ignore_case_sensitive() throws Exception {
+ List<Criterion> criteria = singletonList(Criterion.builder().setKey("query").setOperator(EQ).setValue("Sonar Qube").build());
+
+ ProjectMeasuresQuery underTest = newProjectMeasuresQuery(criteria, emptySet());
+
+ assertThat(underTest.getQueryText().get()).isEqualTo("Sonar Qube");
+ }
+
+ @Test
+ public void fail_to_create_query_having_q_with_no_value() throws Exception {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Query is invalid");
+
+ newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("query").setOperator(EQ).build()),
+ emptySet());
+ }
+
+ @Test
+ public void fail_to_create_query_having_q_with_other_operator_than_equals() throws Exception {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Query should only be used with equals operator");
+
+ newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("query").setOperator(LT).setValue("java").build()),
+ emptySet());
+ }
+
+ @Test
public void do_not_filter_on_projectUuids_if_criteria_non_empty_and_projectUuid_is_null() {
ProjectMeasuresQuery query = newProjectMeasuresQuery(singletonList(Criterion.builder().setKey("ncloc").setOperator(EQ).setValue("10").build()),
null);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
index d5a5a7a3036..82024b61db0 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
@@ -295,6 +295,19 @@ public class SearchProjectsActionTest {
}
@Test
+ public void filter_projects_by_text_query() {
+ OrganizationDto organizationDto = db.organizations().insertForKey("my-org-key-1");
+ insertProjectInDbAndEs(newProjectDto(organizationDto).setKey("sonar-java").setName("Sonar Java"));
+ insertProjectInDbAndEs(newProjectDto(organizationDto).setKey("sonar-groovy").setName("Sonar Groovy"));
+ insertProjectInDbAndEs(newProjectDto(organizationDto).setKey("sonar-markdown").setName("Sonar Markdown"));
+ insertProjectInDbAndEs(newProjectDto(organizationDto).setKey("sonarqube").setName("Sonar Qube"));
+
+ assertThat(call(request.setFilter("query = \"Groovy\"")).getComponentsList()).extracting(Component::getName).containsOnly("Sonar Groovy");
+ assertThat(call(request.setFilter("query = \"oNar\"")).getComponentsList()).extracting(Component::getName).containsOnly("Sonar Java", "Sonar Groovy", "Sonar Markdown", "Sonar Qube");
+ assertThat(call(request.setFilter("query = \"sonar-java\"")).getComponentsList()).extracting(Component::getName).containsOnly("Sonar Java");
+ }
+
+ @Test
public void filter_favourite_projects_with_query_with_or_without_a_specified_organization() {
userSession.logIn();
OrganizationDto organization1 = db.organizations().insert();
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
index 56708d2b8eb..5e027fcbe17 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
@@ -291,6 +291,19 @@ public class ProjectMeasuresIndexTest {
}
@Test
+ public void filter_on_query_text() {
+ ComponentDto windows = newProjectDto(ORG).setUuid("windows").setName("Windows").setKey("project1");
+ ComponentDto apachee = newProjectDto(ORG).setUuid("apachee").setName("apachee").setKey("project2");
+ ComponentDto apache1 = newProjectDto(ORG).setUuid("apache-1").setName("Apache").setKey("project3");
+ ComponentDto apache2 = newProjectDto(ORG).setUuid("apache-2").setName("Apache").setKey("project4");
+ index(newDoc(windows), newDoc(apachee), newDoc(apache1), newDoc(apache2));
+
+ assertResults(new ProjectMeasuresQuery().setQueryText("windows"), windows);
+ assertResults(new ProjectMeasuresQuery().setQueryText("project2"), apachee);
+ assertResults(new ProjectMeasuresQuery().setQueryText("pAch"), apache1, apache2, apachee);
+ }
+
+ @Test
public void filter_on_ids() {
index(
newDoc(PROJECT1),