]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8287 Filter measures in ES
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Tue, 18 Oct 2016 06:34:08 +0000 (08:34 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Tue, 18 Oct 2016 15:01:20 +0000 (17:01 +0200)
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsQueryBuilder.java
server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectMeasuresDoc.java
server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectMeasuresIndex.java
server/sonar-server/src/main/java/org/sonar/server/project/es/ProjectMeasuresIndexDefinition.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsQueryBuilderTest.java
server/sonar-server/src/test/java/org/sonar/server/project/es/ProjectMeasuresIndexTest.java

index 5c68ae9e95b9508b7748afb1a416310a4c89ba1a..10b3c78a5ba71857112707d31388a603d553badb 100644 (file)
@@ -38,6 +38,7 @@ import org.sonarqube.ws.WsComponents.Component;
 import org.sonarqube.ws.WsComponents.SearchProjectsWsResponse;
 import org.sonarqube.ws.client.component.SearchProjectsRequest;
 
+import static com.google.common.base.MoreObjects.firstNonNull;
 import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery;
 import static org.sonar.server.component.ws.SearchProjectsQueryBuilder.build;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
@@ -88,12 +89,11 @@ public class SearchProjectsAction implements ComponentsWsAction {
   }
 
   private SearchResults searchProjects(DbSession dbSession, SearchProjectsRequest request) {
-    String filter = request.getFilter();
-    if (filter != null) {
-      SearchProjectsCriteriaQuery query = build(filter);
-      searchProjectsQueryBuilderValidator.validate(dbSession, query);
-    }
-    SearchIdResult<String> searchResult = index.search(new SearchOptions().setPage(request.getPage(), request.getPageSize()));
+    String filter = firstNonNull(request.getFilter(), "");
+    SearchProjectsCriteriaQuery query = build(filter);
+    searchProjectsQueryBuilderValidator.validate(dbSession, query);
+
+    SearchIdResult<String> searchResult = index.search(query, new SearchOptions().setPage(request.getPage(), request.getPageSize()));
 
     Ordering<ComponentDto> ordering = Ordering.explicit(searchResult.getIds()).onResultOf(ComponentDto::uuid);
     List<ComponentDto> projects = ordering.immutableSortedCopy(dbClient.componentDao().selectByUuids(dbSession, searchResult.getIds()));
index 381db467ea60984390327b87758ad99c2dc87fbe..085ad841d1c82ca8b151bafc432f703b64f02b84 100644 (file)
@@ -25,6 +25,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import org.apache.commons.lang.StringUtils;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.lang.String.format;
@@ -43,7 +44,12 @@ public class SearchProjectsQueryBuilder {
   }
 
   public static SearchProjectsCriteriaQuery build(String filter) {
+    if (StringUtils.isBlank(filter)) {
+      return new SearchProjectsCriteriaQuery();
+    }
+
     SearchProjectsCriteriaQuery query = new SearchProjectsCriteriaQuery();
+
     CRITERIA_SPLITTER.split(filter.toLowerCase(ENGLISH))
       .forEach(criteria -> processCriteria(criteria, query));
     return query;
@@ -60,7 +66,7 @@ public class SearchProjectsQueryBuilder {
 
   public static class SearchProjectsCriteriaQuery {
     public enum Operator {
-      LT("<="), GT(">"), EQ("=");
+      LTE("<="), GT(">"), EQ("=");
 
       String value;
 
@@ -82,7 +88,7 @@ public class SearchProjectsQueryBuilder {
 
     private List<MetricCriteria> metricCriterias = new ArrayList<>();
 
-    SearchProjectsCriteriaQuery addMetricCriteria(MetricCriteria metricCriteria) {
+    public SearchProjectsCriteriaQuery addMetricCriteria(MetricCriteria metricCriteria) {
       metricCriterias.add(metricCriteria);
       return this;
     }
@@ -92,11 +98,11 @@ public class SearchProjectsQueryBuilder {
     }
 
     public static class MetricCriteria {
-      private String metricKey;
-      private Operator operator;
-      private double value;
+      private final String metricKey;
+      private final Operator operator;
+      private final double value;
 
-      private MetricCriteria(String metricKey, Operator operator, double value) {
+      public MetricCriteria(String metricKey, Operator operator, double value) {
         this.metricKey = metricKey;
         this.operator = operator;
         this.value = value;
index e650263e76dd03510f6eca4b23467f24c128bc16..284a8bec17cea5fb673a046ae3d412e842226f8d 100644 (file)
  */
 package org.sonar.server.project.es;
 
+import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.Map;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.server.es.BaseDoc;
 
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
+
 public class ProjectMeasuresDoc extends BaseDoc {
 
   public ProjectMeasuresDoc() {
@@ -78,4 +82,13 @@ public class ProjectMeasuresDoc extends BaseDoc {
     setField(ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT, d);
     return this;
   }
+
+  public Collection<Map<String, Object>> getMeasures() {
+    return getField(FIELD_MEASURES);
+  }
+
+  public ProjectMeasuresDoc setMeasures(Collection<Map<String, Object>> measures) {
+    setField(FIELD_MEASURES, measures);
+    return this;
+  }
 }
index 04486442f25d4e55989f552fbc1dba4d3a600669..6eeef117d6fe9c488d7fbf1626e7ac72a2c25bd1 100644 (file)
 package org.sonar.server.project.es;
 
 import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.index.query.BoolQueryBuilder;
 import org.elasticsearch.index.query.QueryBuilder;
-import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.search.sort.SortOrder;
+import org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery;
+import org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery.MetricCriteria;
 import org.sonar.server.es.BaseIndex;
 import org.sonar.server.es.EsClient;
 import org.sonar.server.es.SearchIdResult;
 import org.sonar.server.es.SearchOptions;
 
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
+import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES_KEY;
+import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES_VALUE;
 import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.FIELD_NAME;
 import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
 import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
 
 public class ProjectMeasuresIndex extends BaseIndex {
 
+  private static final String FIELD_KEY = FIELD_MEASURES + "." + FIELD_MEASURES_KEY;
+  private static final String FIELD_VALUE = FIELD_MEASURES + "." + FIELD_MEASURES_VALUE;
+
   public ProjectMeasuresIndex(EsClient client) {
     super(client);
   }
 
-  public SearchIdResult<String> search(SearchOptions searchOptions) {
-    QueryBuilder condition = QueryBuilders.matchAllQuery();
+  public SearchIdResult<String> search(SearchProjectsCriteriaQuery query, SearchOptions searchOptions) {
+    BoolQueryBuilder metricFilters = boolQuery();
+    query.getMetricCriterias().stream()
+      .map(criteria -> nestedQuery(FIELD_MEASURES, boolQuery()
+        .filter(termQuery(FIELD_KEY, criteria.getMetricKey()))
+        .filter(toValueQuery(criteria))))
+      .forEach(metricFilters::filter);
+    QueryBuilder esQuery = query.getMetricCriterias().isEmpty() ? matchAllQuery() : metricFilters;
 
     SearchRequestBuilder request = getClient()
       .prepareSearch(INDEX_PROJECT_MEASURES)
       .setTypes(TYPE_PROJECT_MEASURES)
       .setFetchSource(false)
-      .setQuery(condition)
+      .setQuery(esQuery)
       .setFrom(searchOptions.getOffset())
       .setSize(searchOptions.getLimit())
       .addSort(FIELD_NAME + "." + SORT_SUFFIX, SortOrder.ASC);
 
     return new SearchIdResult<>(request.get(), id -> id);
   }
+
+  private static QueryBuilder toValueQuery(MetricCriteria criteria) {
+    String fieldName = FIELD_VALUE;
+
+    switch (criteria.getOperator()) {
+      case EQ:
+        return termQuery(fieldName, criteria.getValue());
+      case GT:
+        return rangeQuery(fieldName).gt(criteria.getValue());
+      case LTE:
+        return rangeQuery(fieldName).lte(criteria.getValue());
+      default:
+        throw new IllegalStateException("Metric criteria non supported: " + criteria.getOperator().name());
+    }
+
+  }
 }
index c2f75e8ab62b8ccf944c01232c3f8e67d2344067..61eefd12a481060e584e22d77a419c10aa745fdf 100644 (file)
@@ -30,6 +30,9 @@ public class ProjectMeasuresIndexDefinition implements IndexDefinition {
   public static final String FIELD_KEY = "key";
   public static final String FIELD_NAME = "name";
   public static final String FIELD_ANALYSED_AT = "analysedAt";
+  public static final String FIELD_MEASURES = "measures";
+  public static final String FIELD_MEASURES_KEY = "key";
+  public static final String FIELD_MEASURES_VALUE = "value";
 
   private final Settings settings;
 
@@ -47,6 +50,11 @@ public class ProjectMeasuresIndexDefinition implements IndexDefinition {
     mapping.stringFieldBuilder(FIELD_KEY).disableNorms().build();
     mapping.stringFieldBuilder(FIELD_NAME).enableSorting().enableGramSearch().build();
     mapping.createDateTimeField(FIELD_ANALYSED_AT);
+    mapping.nestedFieldBuilder(FIELD_MEASURES)
+      .addStringFied(FIELD_MEASURES_KEY)
+      .addStringFied(FIELD_MEASURES_VALUE)
+      .build();
+
     // do not store document but only indexation of information
     mapping.setEnableSource(false);
   }
index 1415769ddfe0361cb7eadfd6d603ff72cce99750..36aaea35c707b8778c8dfe033e5da67f23142e1d 100644 (file)
@@ -44,7 +44,7 @@ public class SearchProjectsQueryBuilderTest {
       .extracting(MetricCriteria::getMetricKey, MetricCriteria::getOperator, MetricCriteria::getValue)
       .containsOnly(
         tuple("ncloc", Operator.GT, 10d),
-        tuple("coverage", Operator.LT, 80d));
+        tuple("coverage", Operator.LTE, 80d));
   }
 
   @Test
@@ -53,7 +53,7 @@ public class SearchProjectsQueryBuilderTest {
       .extracting(MetricCriteria::getMetricKey, MetricCriteria::getOperator, MetricCriteria::getValue)
       .containsOnly(
         tuple("ncloc", Operator.GT, 10d),
-        tuple("coverage", Operator.LT, 80d));
+        tuple("coverage", Operator.LTE, 80d));
   }
 
   @Test
@@ -63,6 +63,13 @@ public class SearchProjectsQueryBuilderTest {
       .containsOnly(tuple("ncloc", Operator.GT, 10d));
   }
 
+  @Test
+  public void accept_empty_query() throws Exception {
+    SearchProjectsCriteriaQuery result = build("");
+
+    assertThat(result.getMetricCriterias()).isEmpty();
+  }
+
   @Test
   public void fail_on_unknown_operator() throws Exception {
     expectedException.expect(IllegalArgumentException.class);
@@ -97,11 +104,4 @@ public class SearchProjectsQueryBuilderTest {
     expectedException.expectMessage("Invalid criteria 'ncloc >='");
     build("ncloc >=");
   }
-
-  @Test
-  public void fail_when_no_criteria_provided() throws Exception {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Invalid criteria ''");
-    build("");
-  }
 }
index f67c6bbb43ca28a0613e06abfc1007bc7333e87b..5dae8f3ce8f691d4676583761ac2923fa180744a 100644 (file)
 package org.sonar.server.project.es;
 
 import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.IntStream;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.config.MapSettings;
+import org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery;
+import org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery.MetricCriteria;
+import org.sonar.server.component.ws.SearchProjectsQueryBuilder.SearchProjectsCriteriaQuery.Operator;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.es.SearchIdResult;
 import org.sonar.server.es.SearchOptions;
 
+import static com.google.common.collect.Lists.newArrayList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
 import static org.sonar.server.project.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
 
 public class ProjectMeasuresIndexTest {
 
+  private static final String COVERAGE = "coverage";
+  private static final String NCLOC = "ncloc";
   @Rule
   public EsTester es = new EsTester(new ProjectMeasuresIndexDefinition(new MapSettings()));
 
   private ProjectMeasuresIndex underTest = new ProjectMeasuresIndex(es.client());
 
+  @Test
+  public void empty_search() {
+    List<String> result = underTest.search(new SearchProjectsCriteriaQuery(), new SearchOptions()).getIds();
+
+    assertThat(result).isEmpty();
+  }
+
   @Test
   public void search_sort_by_name_case_insensitive() {
     addDocs(newDoc("P1", "K1", "Windows"),
       newDoc("P3", "K3", "apachee"),
       newDoc("P2", "K2", "Apache"));
 
-    List<String> result = underTest.search(new SearchOptions()).getIds();
+    List<String> result = underTest.search(new SearchProjectsCriteriaQuery(), new SearchOptions()).getIds();
 
     assertThat(result).containsExactly("P2", "P3", "P1");
   }
@@ -56,17 +71,67 @@ public class ProjectMeasuresIndexTest {
     IntStream.rangeClosed(1, 9)
       .forEach(i -> addDocs(newDoc("P" + i, "K" + i, "P" + i)));
 
-    SearchIdResult<String> result = underTest.search(new SearchOptions().setPage(2, 3));
+    SearchIdResult<String> result = underTest.search(new SearchProjectsCriteriaQuery(), new SearchOptions().setPage(2, 3));
 
     assertThat(result.getIds()).containsExactly("P4", "P5", "P6");
     assertThat(result.getTotal()).isEqualTo(9);
   }
 
-  private static ProjectMeasuresDoc newDoc(String uuid, String key, String name) {
-    return new ProjectMeasuresDoc()
-      .setId(uuid)
-      .setKey(key)
-      .setName(name);
+  @Test
+  public void filter_with_lower_than_or_equals() {
+    addDocs(
+      newDoc("P1", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 79d), newMeasure(NCLOC, 10_000d))),
+      newDoc("P2", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d))),
+      newDoc("P3", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 81d), newMeasure(NCLOC, 10_000d))));
+
+    SearchProjectsCriteriaQuery esQuery = new SearchProjectsCriteriaQuery()
+      .addMetricCriteria(new MetricCriteria(COVERAGE, Operator.LTE, 80d));
+    List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
+
+    assertThat(result).containsExactly("P1", "P2");
+  }
+
+  @Test
+  public void filter_with_greater_than() {
+    addDocs(
+      newDoc("P1", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d))),
+      newDoc("P2", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_001d))),
+      newDoc("P3", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_001d))));
+
+    SearchProjectsCriteriaQuery esQuery = new SearchProjectsCriteriaQuery()
+      .addMetricCriteria(new MetricCriteria(NCLOC, Operator.GT, 10_000d));
+    List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
+
+    assertThat(result).containsExactly("P2", "P3");
+  }
+
+  @Test
+  public void filter_with_equals() {
+    addDocs(
+      newDoc("P1", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 79d), newMeasure(NCLOC, 10_000d))),
+      newDoc("P2", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d))),
+      newDoc("P3", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 81d), newMeasure(NCLOC, 10_000d))));
+
+    SearchProjectsCriteriaQuery esQuery = new SearchProjectsCriteriaQuery()
+      .addMetricCriteria(new MetricCriteria(COVERAGE, Operator.EQ, 80d));
+    List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
+
+    assertThat(result).containsExactly("P2");
+  }
+
+  @Test
+  public void filter_on_several_metrics() {
+    addDocs(
+      newDoc("P1", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 81d), newMeasure(NCLOC, 10_001d))),
+      newDoc("P2", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_001d))),
+      newDoc("P3", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 79d), newMeasure(NCLOC, 10_000d))));
+
+    SearchProjectsCriteriaQuery esQuery = new SearchProjectsCriteriaQuery()
+      .addMetricCriteria(new MetricCriteria(COVERAGE, Operator.LTE, 80d))
+      .addMetricCriteria(new MetricCriteria(NCLOC, Operator.GT, 10_000d));
+    List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
+
+    assertThat(result).containsExactly("P2");
   }
 
   private void addDocs(ProjectMeasuresDoc... docs) {
@@ -76,4 +141,15 @@ public class ProjectMeasuresIndexTest {
       Throwables.propagate(e);
     }
   }
+
+  private static ProjectMeasuresDoc newDoc(String uuid, String key, String name) {
+    return new ProjectMeasuresDoc()
+      .setId(uuid)
+      .setKey(key)
+      .setName(name);
+  }
+
+  private Map<String, Object> newMeasure(String key, double value) {
+    return ImmutableMap.of("key", key, "value", value);
+  }
 }