]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8224 SONAR-8225 Sort by name and metric 1669/head
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 16 Feb 2017 10:12:36 +0000 (11:12 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 17 Feb 2017 11:33:20 +0000 (12:33 +0100)
server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidator.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidatorTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/component/ComponentsService.java
sonar-ws/src/main/java/org/sonarqube/ws/client/component/SearchProjectsRequest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/component/SearchWsRequest.java
sonar-ws/src/test/java/org/sonarqube/ws/client/component/ComponentsServiceTest.java

index a0fa062ec0d11db5b2ee1a1355bdcfb6a378fb6e..51118a13380cb88b49e3c0d4e4804530049732d4 100644 (file)
@@ -23,13 +23,16 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.TreeSet;
-import org.sonar.core.util.stream.Collectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.metric.MetricDto;
 import org.sonar.server.measure.index.ProjectMeasuresQuery;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.sonar.core.util.stream.Collectors.toHashSet;
+import static org.sonar.core.util.stream.Collectors.toSet;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
 
 public class ProjectMeasuresQueryValidator {
 
@@ -40,7 +43,7 @@ public class ProjectMeasuresQueryValidator {
   }
 
   public void validate(DbSession dbSession, ProjectMeasuresQuery query) {
-    Set<String> metricKeys = query.getMetricCriteria().stream().map(MetricCriterion::getMetricKey).collect(Collectors.toSet());
+    Set<String> metricKeys = getMetrics(query);
     if (metricKeys.isEmpty()) {
       return;
     }
@@ -50,32 +53,34 @@ public class ProjectMeasuresQueryValidator {
     checkMetricsAreNumerics(dbMetrics);
   }
 
-  private static void checkMetricKeysExists(List<MetricDto> dbMetrics, Set<String> inputMetricKeys) {
-    Set<String> dbMetricsKeys = dbMetrics.stream().map(MetricDto::getKey).collect(Collectors.toSet());
-    Set<String> unknownKeys = inputMetricKeys.stream().filter(metricKey -> !dbMetricsKeys.contains(metricKey)).collect(Collectors.toSet());
-    if (!unknownKeys.isEmpty()) {
-      throw new IllegalArgumentException(String.format("Unknown metric(s) %s", new TreeSet<>(unknownKeys)));
+  private static Set<String> getMetrics(ProjectMeasuresQuery query) {
+    Set<String> metricKeys = query.getMetricCriteria().stream().map(MetricCriterion::getMetricKey).collect(toHashSet());
+    if (query.getSort() != null && !SORT_BY_NAME.equals(query.getSort())) {
+      metricKeys.add(query.getSort());
     }
+    return metricKeys;
+  }
+
+  private static void checkMetricKeysExists(List<MetricDto> dbMetrics, Set<String> inputMetricKeys) {
+    Set<String> dbMetricsKeys = dbMetrics.stream().map(MetricDto::getKey).collect(toSet());
+    Set<String> unknownKeys = inputMetricKeys.stream().filter(metricKey -> !dbMetricsKeys.contains(metricKey)).collect(toSet());
+    checkArgument(unknownKeys.isEmpty(), "Unknown metric(s) %s", new TreeSet<>(unknownKeys));
   }
 
   private static void checkMetricsAreEnabled(List<MetricDto> dbMetrics) {
     Set<String> invalidKeys = dbMetrics.stream()
       .filter(metricDto -> !metricDto.isEnabled())
       .map(MetricDto::getKey)
-      .collect(Collectors.toSet());
-    if (!invalidKeys.isEmpty()) {
-      throw new IllegalArgumentException(String.format("Following metrics are disabled : %s", new TreeSet<>(invalidKeys)));
-    }
+      .collect(toSet());
+    checkArgument(invalidKeys.isEmpty(), "Following metrics are disabled : %s", new TreeSet<>(invalidKeys));
   }
 
   private static void checkMetricsAreNumerics(List<MetricDto> dbMetrics) {
     Set<String> invalidKeys = dbMetrics.stream()
       .filter(MetricDto::isDataType)
       .map(MetricDto::getKey)
-      .collect(Collectors.toSet());
-    if (!invalidKeys.isEmpty()) {
-      throw new IllegalArgumentException(String.format("Following metrics are not numeric : %s", new TreeSet<>(invalidKeys)));
-    }
+      .collect(toSet());
+    checkArgument(invalidKeys.isEmpty(), "Following metrics are not numeric : %s", new TreeSet<>(invalidKeys));
   }
 
 }
index ab18e47eca5d8352f608077673dc4be89e263367..15300acf08cd183b75d2c00c90c236d9c61b4be6 100644 (file)
@@ -59,11 +59,13 @@ import org.sonarqube.ws.client.component.SearchProjectsRequest;
 
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static java.lang.String.format;
+import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
 import static org.sonar.core.util.stream.Collectors.toSet;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.hasIsFavoriteCriterion;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.toCriteria;
 import static org.sonar.server.measure.index.ProjectMeasuresIndex.SUPPORTED_FACETS;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
 import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FILTER;
@@ -72,6 +74,7 @@ import static org.sonarqube.ws.client.component.SearchProjectsRequest.DEFAULT_PA
 import static org.sonarqube.ws.client.component.SearchProjectsRequest.MAX_PAGE_SIZE;
 
 public class SearchProjectsAction implements ComponentsWsAction {
+
   private final DbClient dbClient;
   private final ProjectMeasuresIndex index;
   private final ProjectMeasuresQueryValidator queryValidator;
@@ -131,6 +134,16 @@ public class SearchProjectsAction implements ComponentsWsAction {
         " <li>'WARN' for Warning</li>" +
         " <li>'ERROR' for Failed</li>" +
         "</ul>");
+
+    action.createParam(Param.SORT)
+      .setDescription("Sort projects by numeric metric key or by name.<br/>" +
+        "See '%s' parameter description for the possible metric values", PARAM_FILTER)
+      .setDefaultValue(SORT_BY_NAME)
+      .setExampleValue(NCLOC_KEY);
+    action.createParam(Param.ASCENDING)
+      .setDescription("Ascending sort")
+      .setBooleanPossibleValues()
+      .setDefaultValue(true);
   }
 
   @Override
@@ -169,12 +182,14 @@ public class SearchProjectsAction implements ComponentsWsAction {
   }
 
   private SearchResults searchData(DbSession dbSession, SearchProjectsRequest request, @Nullable OrganizationDto organization) {
-    List<String> criteria = toCriteria(firstNonNull(request.getFilter(), ""));
-
     Set<String> favoriteProjectUuids = loadFavoriteProjectUuids(dbSession);
+
+    List<String> criteria = toCriteria(firstNonNull(request.getFilter(), ""));
     Set<String> projectUuids = buildFilterOnFavoriteProjectUuids(criteria, favoriteProjectUuids);
 
-    ProjectMeasuresQuery query = newProjectMeasuresQuery(criteria, projectUuids);
+    ProjectMeasuresQuery query = newProjectMeasuresQuery(criteria, projectUuids)
+      .setSort(request.getSort())
+      .setAsc(request.getAsc());
     Optional.ofNullable(organization)
       .map(OrganizationDto::getUuid)
       .ifPresent(query::setOrganizationUuid);
@@ -226,6 +241,8 @@ public class SearchProjectsAction implements ComponentsWsAction {
     SearchProjectsRequest.Builder request = SearchProjectsRequest.builder()
       .setOrganization(httpRequest.param(PARAM_ORGANIZATION))
       .setFilter(httpRequest.param(PARAM_FILTER))
+      .setSort(httpRequest.mandatoryParam(Param.SORT))
+      .setAsc(httpRequest.mandatoryParamAsBoolean(Param.ASCENDING))
       .setPage(httpRequest.mandatoryParamAsInt(Param.PAGE))
       .setPageSize(httpRequest.mandatoryParamAsInt(Param.PAGE_SIZE));
     if (httpRequest.hasParam(Param.FACETS)) {
index dd481748d953fcd3998a3f8ce1fe2be96ef77fbd..7a2abab4db07d524079e110d7bc85aa426843ddc 100644 (file)
@@ -32,7 +32,7 @@ import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
 import org.elasticsearch.search.aggregations.AggregationBuilders;
 import org.elasticsearch.search.aggregations.bucket.range.RangeBuilder;
-import org.elasticsearch.search.sort.SortOrder;
+import org.elasticsearch.search.sort.FieldSortBuilder;
 import org.sonar.api.measures.Metric;
 import org.sonar.server.es.BaseIndex;
 import org.sonar.server.es.DefaultIndexSettingsElement;
@@ -50,6 +50,8 @@ import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
 import static org.elasticsearch.search.aggregations.AggregationBuilders.filters;
+import static org.elasticsearch.search.sort.SortOrder.ASC;
+import static org.elasticsearch.search.sort.SortOrder.DESC;
 import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
 import static org.sonar.api.measures.CoreMetrics.COVERAGE_KEY;
 import static org.sonar.api.measures.CoreMetrics.DUPLICATED_LINES_DENSITY_KEY;
@@ -57,14 +59,14 @@ import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
 import static org.sonar.api.measures.CoreMetrics.RELIABILITY_RATING_KEY;
 import static org.sonar.api.measures.CoreMetrics.SECURITY_RATING_KEY;
 import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_KEY;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES_KEY;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES_VALUE;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_NAME;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_ORGANIZATION_UUID;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_QUALITY_GATE;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURE;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
 
 public class ProjectMeasuresIndex extends BaseIndex {
 
@@ -77,8 +79,8 @@ public class ProjectMeasuresIndex extends BaseIndex {
     SECURITY_RATING_KEY,
     ALERT_STATUS_KEY);
 
-  private static final String FIELD_KEY = FIELD_MEASURES + "." + FIELD_MEASURES_KEY;
-  private static final String FIELD_VALUE = FIELD_MEASURES + "." + FIELD_MEASURES_VALUE;
+  private static final String FIELD_MEASURES_KEY = FIELD_MEASURES + "." + ProjectMeasuresIndexDefinition.FIELD_MEASURES_KEY;
+  private static final String FIELD_MEASURES_VALUE = FIELD_MEASURES + "." + ProjectMeasuresIndexDefinition.FIELD_MEASURES_VALUE;
 
   private final AuthorizationTypeSupport authorizationTypeSupport;
 
@@ -93,8 +95,7 @@ public class ProjectMeasuresIndex extends BaseIndex {
       .setTypes(TYPE_PROJECT_MEASURE)
       .setFetchSource(false)
       .setFrom(searchOptions.getOffset())
-      .setSize(searchOptions.getLimit())
-      .addSort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), SortOrder.ASC);
+      .setSize(searchOptions.getLimit());
 
     BoolQueryBuilder esFilter = boolQuery();
     Map<String, QueryBuilder> filters = createFilters(query);
@@ -102,9 +103,30 @@ public class ProjectMeasuresIndex extends BaseIndex {
     requestBuilder.setQuery(esFilter);
 
     addFacets(requestBuilder, searchOptions, filters);
+    addSort(query, requestBuilder);
     return new SearchIdResult<>(requestBuilder.get(), id -> id);
   }
 
+  private static void addSort(ProjectMeasuresQuery query, SearchRequestBuilder requestBuilder) {
+    String sort = query.getSort();
+    if (sort == null || SORT_BY_NAME.equals(sort)) {
+      requestBuilder.addSort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), query.isAsc() ? ASC : DESC);
+    } else {
+      addNameSort(query, requestBuilder, sort);
+      requestBuilder.addSort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), ASC);
+    }
+    // last sort is by key in order to be deterministic when same value
+    requestBuilder.addSort(FIELD_KEY, ASC);
+  }
+
+  private static void addNameSort(ProjectMeasuresQuery query, SearchRequestBuilder requestBuilder, String sort) {
+    requestBuilder.addSort(
+      new FieldSortBuilder(FIELD_MEASURES_VALUE)
+        .setNestedPath(FIELD_MEASURES)
+        .setNestedFilter(termQuery(FIELD_MEASURES_KEY, sort))
+        .order(query.isAsc() ? ASC : DESC));
+  }
+
   private static void addFacets(SearchRequestBuilder esSearch, SearchOptions options, Map<String, QueryBuilder> filters) {
     if (!options.getFacets().isEmpty()) {
       if (options.getFacets().contains(NCLOC_KEY)) {
@@ -151,7 +173,7 @@ public class ProjectMeasuresIndex extends BaseIndex {
 
   private static AbstractAggregationBuilder createRangeFacet(String metricKey, List<Double> thresholds) {
     RangeBuilder rangeAgg = AggregationBuilders.range(metricKey)
-      .field(FIELD_VALUE);
+      .field(FIELD_MEASURES_VALUE);
     final int lastIndex = thresholds.size() - 1;
     IntStream.range(0, thresholds.size())
       .forEach(i -> {
@@ -169,7 +191,7 @@ public class ProjectMeasuresIndex extends BaseIndex {
       .path(FIELD_MEASURES)
       .subAggregation(
         AggregationBuilders.filter("filter_" + metricKey)
-          .filter(termsQuery(FIELD_KEY, metricKey))
+          .filter(termsQuery(FIELD_MEASURES_KEY, metricKey))
           .subAggregation(rangeAgg));
   }
 
@@ -178,13 +200,13 @@ public class ProjectMeasuresIndex extends BaseIndex {
       .path(FIELD_MEASURES)
       .subAggregation(
         AggregationBuilders.filter("filter_" + metricKey)
-          .filter(termsQuery(FIELD_KEY, metricKey))
+          .filter(termsQuery(FIELD_MEASURES_KEY, metricKey))
           .subAggregation(filters(metricKey)
-            .filter("1", termQuery(FIELD_VALUE, 1d))
-            .filter("2", termQuery(FIELD_VALUE, 2d))
-            .filter("3", termQuery(FIELD_VALUE, 3d))
-            .filter("4", termQuery(FIELD_VALUE, 4d))
-            .filter("5", termQuery(FIELD_VALUE, 5d))));
+            .filter("1", termQuery(FIELD_MEASURES_VALUE, 1d))
+            .filter("2", termQuery(FIELD_MEASURES_VALUE, 2d))
+            .filter("3", termQuery(FIELD_MEASURES_VALUE, 3d))
+            .filter("4", termQuery(FIELD_MEASURES_VALUE, 4d))
+            .filter("5", termQuery(FIELD_MEASURES_VALUE, 5d))));
   }
 
   private static AbstractAggregationBuilder createQualityGateFacet() {
@@ -204,7 +226,7 @@ public class ProjectMeasuresIndex extends BaseIndex {
       entry.getValue()
         .stream()
         .map(criterion -> nestedQuery(FIELD_MEASURES, boolQuery()
-          .filter(termQuery(FIELD_KEY, criterion.getMetricKey()))
+          .filter(termQuery(FIELD_MEASURES_KEY, criterion.getMetricKey()))
           .filter(toValueQuery(criterion))))
         .forEach(metricFilters::must);
       filters.put(entry.getKey(), metricFilters);
@@ -223,7 +245,7 @@ public class ProjectMeasuresIndex extends BaseIndex {
   }
 
   private static QueryBuilder toValueQuery(MetricCriterion criterion) {
-    String fieldName = FIELD_VALUE;
+    String fieldName = FIELD_MEASURES_VALUE;
 
     switch (criterion.getOperator()) {
       case GT:
index a70e8ac945db73aca504eee36a6e25f87b16d83a..cf52b20fb3b374218b04cee1c6103475098349ce 100644 (file)
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.api.measures.Metric;
 
@@ -31,10 +32,15 @@ import static java.util.Arrays.stream;
 import static java.util.Objects.requireNonNull;
 
 public class ProjectMeasuresQuery {
+
+  public static final String SORT_BY_NAME = "name";
+
   private List<MetricCriterion> metricCriteria = new ArrayList<>();
   private Metric.Level qualityGateStatus;
-  private String organizationUuid = null;
-  private Set<String> projectUuids = null;
+  private String organizationUuid;
+  private Set<String> projectUuids;
+  private String sort;
+  private boolean asc = true;
 
   public ProjectMeasuresQuery addMetricCriterion(MetricCriterion metricCriterion) {
     this.metricCriteria.add(metricCriterion);
@@ -72,25 +78,23 @@ public class ProjectMeasuresQuery {
     return Optional.ofNullable(projectUuids);
   }
 
-  public enum Operator {
-    LT("<"), LTE("<="), GT(">"), GTE(">="), EQ("=");
-
-    String value;
+  @CheckForNull
+  public String getSort() {
+    return sort;
+  }
 
-    Operator(String value) {
-      this.value = value;
-    }
+  public ProjectMeasuresQuery setSort(@Nullable String sort) {
+    this.sort = sort;
+    return this;
+  }
 
-    String getValue() {
-      return value;
-    }
+  public boolean isAsc() {
+    return asc;
+  }
 
-    public static Operator getByValue(String value) {
-      return stream(Operator.values())
-        .filter(operator -> operator.getValue().equals(value))
-        .findFirst()
-        .orElseThrow(() -> new IllegalArgumentException(format("Unknown operator '%s'", value)));
-    }
+  public ProjectMeasuresQuery setAsc(boolean asc) {
+    this.asc = asc;
+    return this;
   }
 
   public static class MetricCriterion {
@@ -116,4 +120,25 @@ public class ProjectMeasuresQuery {
       return value;
     }
   }
+
+  public enum Operator {
+    LT("<"), LTE("<="), GT(">"), GTE(">="), EQ("=");
+
+    String value;
+
+    Operator(String value) {
+      this.value = value;
+    }
+
+    String getValue() {
+      return value;
+    }
+
+    public static Operator getByValue(String value) {
+      return stream(Operator.values())
+        .filter(operator -> operator.getValue().equals(value))
+        .findFirst()
+        .orElseThrow(() -> new IllegalArgumentException(format("Unknown operator '%s'", value)));
+    }
+  }
 }
index 5f4c23b610d8c35a122533750c9a603eab5ec197..b8bc74243885e7cffb62b32eb903215d51b66ad6 100644 (file)
@@ -27,7 +27,6 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.metric.MetricDto;
-import org.sonar.server.tester.UserSessionRule;
 
 import static java.util.Collections.emptyList;
 import static java.util.Collections.emptySet;
@@ -45,9 +44,6 @@ public class ProjectMeasuresQueryValidatorTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  @Rule
-  public UserSessionRule userSession = UserSessionRule.standalone();
-
   @Rule
   public DbTester db = DbTester.create(System2.INSTANCE);
 
@@ -68,6 +64,21 @@ public class ProjectMeasuresQueryValidatorTest {
     underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("ncloc > 10"), emptySet()));
   }
 
+  @Test
+  public void does_not_fail_when_sort_is_by_name() throws Exception {
+    insertValidMetric("ncloc");
+
+    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("ncloc > 10"), emptySet()).setSort("name"));
+  }
+
+  @Test
+  public void does_not_fail_when_sort_contains_an_existing_metric() throws Exception {
+    insertValidMetric("ncloc");
+    insertValidMetric("debt");
+
+    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("ncloc > 10"), emptySet()).setSort("debt"));
+  }
+
   @Test
   public void fail_when_metric_are_not_numeric() throws Exception {
     insertMetric(createValidMetric("ncloc").setValueType(INT.name()));
@@ -84,10 +95,11 @@ public class ProjectMeasuresQueryValidatorTest {
   @Test
   public void fail_when_metric_is_disabled() throws Exception {
     insertMetric(createValidMetric("ncloc").setEnabled(false));
+    insertMetric(createValidMetric("debt").setEnabled(false));
 
     expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Following metrics are disabled : [ncloc]");
-    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("ncloc > 10"), emptySet()));
+    expectedException.expectMessage("Following metrics are disabled : [debt, ncloc]");
+    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("ncloc > 10"), emptySet()).setSort("debt"));
   }
 
   @Test
@@ -95,8 +107,8 @@ public class ProjectMeasuresQueryValidatorTest {
     insertValidMetric("ncloc");
 
     expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Unknown metric(s) [unknown]");
-    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("unknown > 10"), emptySet()));
+    expectedException.expectMessage("Unknown metric(s) [debt, unknown]");
+    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("unknown > 10"), emptySet()).setSort("debt"));
   }
 
   @Test
@@ -104,8 +116,8 @@ public class ProjectMeasuresQueryValidatorTest {
     insertValidMetric("ncloc");
 
     expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Unknown metric(s) [coverage, debt]");
-    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("debt > 10 AND ncloc <= 20 AND coverage > 30"), emptySet()));
+    expectedException.expectMessage("Unknown metric(s) [coverage, debt, duplications]");
+    underTest.validate(dbSession, newProjectMeasuresQuery(toCriteria("debt > 10 AND ncloc <= 20 AND coverage > 30"), emptySet()).setSort("duplications"));
   }
 
   private void insertValidMetric(String metricKey) {
index 033306fc89c1c0b233d67ea0f7d98bf8b8e683c8..0e3a9b87f401c18aedc5810db4f9dbf65c812dc1 100644 (file)
@@ -62,9 +62,16 @@ import org.sonarqube.ws.client.component.SearchProjectsRequest;
 import static com.google.common.collect.Lists.newArrayList;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
+import static java.util.Optional.ofNullable;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
 import static org.sonar.api.measures.Metric.ValueType.INT;
+import static org.sonar.api.server.ws.WebService.Param.ASCENDING;
+import static org.sonar.api.server.ws.WebService.Param.FACETS;
+import static org.sonar.api.server.ws.WebService.Param.PAGE;
+import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
+import static org.sonar.api.server.ws.WebService.Param.SORT;
+import static org.sonar.core.util.stream.Collectors.toList;
 import static org.sonar.db.component.ComponentTesting.newDeveloper;
 import static org.sonar.db.component.ComponentTesting.newDirectory;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
@@ -115,10 +122,21 @@ public class SearchProjectsActionTest {
     assertThat(def.isInternal()).isTrue();
     assertThat(def.isPost()).isFalse();
     assertThat(def.responseExampleAsString()).isNotEmpty();
+    assertThat(def.params().stream().map(Param::key).collect(toList())).containsOnly("organization", "filter", "facets", "s", "asc", "ps", "p");
+
     Param organization = def.param("organization");
     assertThat(organization.isRequired()).isFalse();
     assertThat(organization.description()).isEqualTo("the organization to search projects in");
     assertThat(organization.since()).isEqualTo("6.3");
+
+    Param sort = def.param("s");
+    assertThat(sort.defaultValue()).isEqualTo("name");
+    assertThat(sort.exampleValue()).isEqualTo("ncloc");
+    assertThat(sort.possibleValues()).isNull();
+
+    Param asc = def.param("asc");
+    assertThat(asc.defaultValue()).isEqualTo("true");
+    assertThat(asc.possibleValues()).containsOnly("true", "false", "yes", "no");
   }
 
   @Test
@@ -348,6 +366,7 @@ public class SearchProjectsActionTest {
     insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Markdown"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d)));
     insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Qube"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 500_001d)));
     insertMetrics(COVERAGE, NCLOC);
+
     SearchProjectsWsResponse result = call(request.setFacets(singletonList(NCLOC)));
 
     Common.Facet facet = result.getFacets().getFacetsList().stream()
@@ -366,11 +385,53 @@ public class SearchProjectsActionTest {
   }
 
   @Test
-  public void fail_if_metric_is_unknown() {
+  public void default_sort_is_by_ascending_name() throws Exception {
+    OrganizationDto organization = db.getDefaultOrganization();
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Java"), newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 5d)));
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Groovy"), newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 5d)));
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Markdown"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d)));
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Qube"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 500_001d)));
+
+    SearchProjectsWsResponse result = call(request);
+
+    assertThat(result.getComponentsList()).extracting(Component::getName).containsExactly("Sonar Groovy", "Sonar Java", "Sonar Markdown", "Sonar Qube");
+  }
+
+  @Test
+  public void sort_by_name() throws Exception {
+    OrganizationDto organization = db.getDefaultOrganization();
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Java"), newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 5d)));
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Groovy"), newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 5d)));
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Markdown"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d)));
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Qube"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 500_001d)));
+
+    assertThat(call(request.setSort("name").setAsc(true)).getComponentsList()).extracting(Component::getName)
+      .containsExactly("Sonar Groovy", "Sonar Java", "Sonar Markdown", "Sonar Qube");
+    assertThat(call(request.setSort("name").setAsc(false)).getComponentsList()).extracting(Component::getName)
+      .containsExactly("Sonar Qube", "Sonar Markdown", "Sonar Java", "Sonar Groovy");
+  }
+
+  @Test
+  public void sort_by_coverage() throws Exception {
+    OrganizationDto organization = db.getDefaultOrganization();
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Java"), newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 5d)));
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Groovy"), newArrayList(newMeasure(COVERAGE, 81), newMeasure(NCLOC, 5d)));
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Markdown"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 10_000d)));
+    insertProjectInDbAndEs(newProjectDto(organization).setName("Sonar Qube"), newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 500_001d)));
+    insertMetrics(COVERAGE);
+
+    assertThat(call(request.setSort(COVERAGE).setAsc(true)).getComponentsList()).extracting(Component::getName)
+      .containsExactly("Sonar Markdown", "Sonar Qube", "Sonar Groovy", "Sonar Java");
+    assertThat(call(request.setSort(COVERAGE).setAsc(false)).getComponentsList()).extracting(Component::getName)
+      .containsExactly("Sonar Groovy", "Sonar Java", "Sonar Markdown", "Sonar Qube");
+  }
+
+  @Test
+  public void fail_when_metrics_are_unknown() {
     expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Unknown metric(s) [coverage]");
+    expectedException.expectMessage("Unknown metric(s) [coverage, debt]");
 
-    request.setFilter("coverage > 80");
+    request.setFilter("coverage > 80").setSort("debt");
 
     call(request);
   }
@@ -386,19 +447,13 @@ public class SearchProjectsActionTest {
     SearchProjectsRequest wsRequest = requestBuilder.build();
     TestRequest httpRequest = ws.newRequest()
       .setMediaType(MediaTypes.PROTOBUF);
-
-    String organization = wsRequest.getOrganization();
-    if (organization != null) {
-      httpRequest.setParam(PARAM_ORGANIZATION, organization);
-    }
-    httpRequest.setParam(Param.PAGE, String.valueOf(wsRequest.getPage()));
-    httpRequest.setParam(Param.PAGE_SIZE, String.valueOf(wsRequest.getPageSize()));
-    String filter = wsRequest.getFilter();
-    if (filter != null) {
-      httpRequest.setParam(PARAM_FILTER, filter);
-    }
-    httpRequest.setParam(Param.FACETS, Joiner.on(",").join(wsRequest.getFacets()));
-
+    ofNullable(wsRequest.getOrganization()).ifPresent(organization -> httpRequest.setParam(PARAM_ORGANIZATION, organization));
+    ofNullable(wsRequest.getFilter()).ifPresent(filter -> httpRequest.setParam(PARAM_FILTER, filter));
+    ofNullable(wsRequest.getSort()).ifPresent(sort -> httpRequest.setParam(SORT, sort));
+    ofNullable(wsRequest.getAsc()).ifPresent(asc -> httpRequest.setParam(ASCENDING, Boolean.toString(asc)));
+    httpRequest.setParam(PAGE, String.valueOf(wsRequest.getPage()));
+    httpRequest.setParam(PAGE_SIZE, String.valueOf(wsRequest.getPageSize()));
+    httpRequest.setParam(FACETS, Joiner.on(",").join(wsRequest.getFacets()));
     try {
       return SearchProjectsWsResponse.parseFrom(httpRequest.execute().getInputStream());
     } catch (IOException e) {
index ecb6a730b17502d44d7602bd231fedaeb2f72101..c6ba98edeb49c47172de42f08313cb90d4906bc3 100644 (file)
@@ -71,9 +71,10 @@ public class ProjectMeasuresIndexTest {
   private static final String NCLOC = "ncloc";
 
   private static final OrganizationDto ORG = OrganizationTesting.newOrganizationDto();
-  private static final ComponentDto PROJECT1 = newProjectDto(ORG);
-  private static final ComponentDto PROJECT2 = newProjectDto(ORG);
-  private static final ComponentDto PROJECT3 = newProjectDto(ORG);
+  private static final ComponentDto PROJECT1 = newProjectDto(ORG).setUuid("Project-1").setName("Project 1").setKey("key-1");
+  private static final ComponentDto PROJECT2 = newProjectDto(ORG).setUuid("Project-2").setName("Project 2").setKey("key-2");
+  private static final ComponentDto PROJECT3 = newProjectDto(ORG).setUuid("Project-3").setName("Project 3").setKey("key-3");
+  private static final ComponentDto PROJECT4 = newProjectDto(ORG).setUuid("Project-4").setName("Project 4").setKey("key-4");
   private static final UserDto USER1 = newUserDto();
   private static final UserDto USER2 = newUserDto();
   private static final GroupDto GROUP1 = newGroupDto();
@@ -95,19 +96,58 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void search_sort_by_case_insensitive_name() {
-    ComponentDto projectA = newProjectDto(ORG).setName("Windows");
-    ComponentDto projectB = newProjectDto(ORG).setName("apachee");
-    ComponentDto projectC = newProjectDto(ORG).setName("Apache");
-    index(newDoc(projectA), newDoc(projectB), newDoc(projectC));
+  public void default_sort_is_by_ascending_case_insensitive_name_then_by_key() {
+    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(), projectC, projectB, projectA);
+    assertResults(new ProjectMeasuresQuery(), apache1, apache2, apachee, windows);
   }
 
   @Test
-  public void search_paginate_results() {
+  public void sort_by_insensitive_name() {
+    ComponentDto windows = newProjectDto(ORG).setUuid("windows").setName("Windows");
+    ComponentDto apachee = newProjectDto(ORG).setUuid("apachee").setName("apachee");
+    ComponentDto apache = newProjectDto(ORG).setUuid("apache").setName("Apache");
+    index(newDoc(windows), newDoc(apachee), newDoc(apache));
+
+    assertResults(new ProjectMeasuresQuery().setSort("name").setAsc(true), apache, apachee, windows);
+    assertResults(new ProjectMeasuresQuery().setSort("name").setAsc(false), windows, apachee, apache);
+  }
+
+  @Test
+  public void sort_by_ncloc() {
+    index(
+      newDoc(PROJECT1, NCLOC, 15_000d),
+      newDoc(PROJECT2, NCLOC, 30_000d),
+      newDoc(PROJECT3, NCLOC, 1_000d));
+
+    assertResults(new ProjectMeasuresQuery().setSort("ncloc").setAsc(true), PROJECT3, PROJECT1, PROJECT2);
+    assertResults(new ProjectMeasuresQuery().setSort("ncloc").setAsc(false), PROJECT2, PROJECT1, PROJECT3);
+  }
+
+  @Test
+  public void sort_by_a_metric_then_by_name_then_by_key() {
+    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, NCLOC, 10_000d),
+      newDoc(apachee, NCLOC, 5_000d),
+      newDoc(apache1, NCLOC, 5_000d),
+      newDoc(apache2, NCLOC, 5_000d));
+
+    assertResults(new ProjectMeasuresQuery().setSort("ncloc").setAsc(true), apache1, apache2, apachee, windows);
+    assertResults(new ProjectMeasuresQuery().setSort("ncloc").setAsc(false), windows, apache1, apache2, apachee);
+  }
+
+  @Test
+  public void paginate_results() {
     IntStream.rangeClosed(1, 9)
-        .forEach(i -> index(newDoc(newProjectDto(ORG, "P" + i))));
+      .forEach(i -> index(newDoc(newProjectDto(ORG, "P" + i))));
 
     SearchIdResult<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().setPage(2, 3));
 
@@ -118,12 +158,12 @@ public class ProjectMeasuresIndexTest {
   @Test
   public void filter_with_lower_than() {
     index(
-        newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
-        newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
-        newDoc(PROJECT3, COVERAGE, 81d, NCLOC, 10_000d));
+      newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
+      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
+      newDoc(PROJECT3, COVERAGE, 81d, NCLOC, 10_000d));
 
     ProjectMeasuresQuery query = new ProjectMeasuresQuery()
-        .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LT, 80d));
+      .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LT, 80d));
 
     assertResults(query, PROJECT1);
   }
@@ -131,12 +171,12 @@ public class ProjectMeasuresIndexTest {
   @Test
   public void filter_with_lower_than_or_equals() {
     index(
-        newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
-        newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
-        newDoc(PROJECT3, COVERAGE, 81d, NCLOC, 10_000d));
+      newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
+      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
+      newDoc(PROJECT3, COVERAGE, 81d, NCLOC, 10_000d));
 
     ProjectMeasuresQuery query = new ProjectMeasuresQuery()
-        .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LTE, 80d));
+      .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LTE, 80d));
 
     assertResults(query, PROJECT1, PROJECT2);
   }
@@ -144,9 +184,9 @@ public class ProjectMeasuresIndexTest {
   @Test
   public void filter_with_greater_than() {
     index(
-        newDoc(PROJECT1, COVERAGE, 80d, NCLOC, 30_000d),
-        newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 30_001d),
-        newDoc(PROJECT3, COVERAGE, 80d, NCLOC, 30_001d));
+      newDoc(PROJECT1, COVERAGE, 80d, NCLOC, 30_000d),
+      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 30_001d),
+      newDoc(PROJECT3, COVERAGE, 80d, NCLOC, 30_001d));
 
     ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(new MetricCriterion(NCLOC, Operator.GT, 30_000d));
     assertResults(query, PROJECT2, PROJECT3);
@@ -158,9 +198,9 @@ public class ProjectMeasuresIndexTest {
   @Test
   public void filter_with_greater_than_or_equals() {
     index(
-        newDoc(PROJECT1, COVERAGE, 80d, NCLOC, 30_000d),
-        newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 30_001d),
-        newDoc(PROJECT3, COVERAGE, 80d, NCLOC, 30_001d));
+      newDoc(PROJECT1, COVERAGE, 80d, NCLOC, 30_000d),
+      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 30_001d),
+      newDoc(PROJECT3, COVERAGE, 80d, NCLOC, 30_001d));
 
     ProjectMeasuresQuery query = new ProjectMeasuresQuery().addMetricCriterion(new MetricCriterion(NCLOC, Operator.GTE, 30_001d));
     assertResults(query, PROJECT2, PROJECT3);
@@ -172,12 +212,12 @@ public class ProjectMeasuresIndexTest {
   @Test
   public void filter_with_equals() {
     index(
-        newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
-        newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
-        newDoc(PROJECT3, COVERAGE, 81d, NCLOC, 10_000d));
+      newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
+      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
+      newDoc(PROJECT3, COVERAGE, 81d, NCLOC, 10_000d));
 
     ProjectMeasuresQuery query = new ProjectMeasuresQuery()
-        .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.EQ, 80d));
+      .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.EQ, 80d));
 
     assertResults(query, PROJECT2);
   }
@@ -185,23 +225,23 @@ public class ProjectMeasuresIndexTest {
   @Test
   public void filter_on_several_metrics() {
     index(
-        newDoc(PROJECT1, COVERAGE, 81d, NCLOC, 10_001d),
-        newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_001d),
-        newDoc(PROJECT3, COVERAGE, 79d, NCLOC, 10_000d));
+      newDoc(PROJECT1, COVERAGE, 81d, NCLOC, 10_001d),
+      newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_001d),
+      newDoc(PROJECT3, COVERAGE, 79d, NCLOC, 10_000d));
 
     ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery()
-        .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LTE, 80d))
-        .addMetricCriterion(new MetricCriterion(NCLOC, Operator.GT, 10_000d))
-        .addMetricCriterion(new MetricCriterion(NCLOC, Operator.LT, 11_000d));
+      .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LTE, 80d))
+      .addMetricCriterion(new MetricCriterion(NCLOC, Operator.GT, 10_000d))
+      .addMetricCriterion(new MetricCriterion(NCLOC, Operator.LT, 11_000d));
     assertResults(esQuery, PROJECT2);
   }
 
   @Test
   public void filter_on_quality_gate_status() {
     index(
-        newDoc(PROJECT1).setQualityGate("OK"),
-        newDoc(PROJECT2).setQualityGate("OK"),
-        newDoc(PROJECT3).setQualityGate("WARN"));
+      newDoc(PROJECT1).setQualityGate("OK"),
+      newDoc(PROJECT2).setQualityGate("OK"),
+      newDoc(PROJECT3).setQualityGate("WARN"));
 
     ProjectMeasuresQuery query = new ProjectMeasuresQuery().setQualityGateStatus(OK);
     assertResults(query, PROJECT1, PROJECT2);
@@ -210,9 +250,9 @@ public class ProjectMeasuresIndexTest {
   @Test
   public void filter_on_ids() {
     index(
-        newDoc(PROJECT1),
-        newDoc(PROJECT2),
-        newDoc(PROJECT3));
+      newDoc(PROJECT1),
+      newDoc(PROJECT2),
+      newDoc(PROJECT3));
 
     ProjectMeasuresQuery query = new ProjectMeasuresQuery().setProjectUuids(newHashSet(PROJECT1.uuid(), PROJECT3.uuid()));
     assertResults(query, PROJECT1, PROJECT3);
@@ -275,8 +315,8 @@ public class ProjectMeasuresIndexTest {
   @Test
   public void does_not_return_facet_when_no_facets_in_options() throws Exception {
     index(
-        newDoc(PROJECT1, NCLOC, 10d, COVERAGE_KEY, 30d, MAINTAINABILITY_RATING, 3d)
-            .setQualityGate(OK.name()));
+      newDoc(PROJECT1, NCLOC, 10d, COVERAGE_KEY, 30d, MAINTAINABILITY_RATING, 3d)
+        .setQualityGate(OK.name()));
 
     Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getFacets();
 
@@ -286,585 +326,585 @@ public class ProjectMeasuresIndexTest {
   @Test
   public void facet_ncloc() {
     index(
-        // 3 docs with ncloc<1K
-        newDoc(NCLOC, 0d),
-        newDoc(NCLOC, 0d),
-        newDoc(NCLOC, 999d),
-        // 2 docs with ncloc>=1K and ncloc<10K
-        newDoc(NCLOC, 1_000d),
-        newDoc(NCLOC, 9_999d),
-        // 4 docs with ncloc>=10K and ncloc<100K
-        newDoc(NCLOC, 10_000d),
-        newDoc(NCLOC, 10_000d),
-        newDoc(NCLOC, 11_000d),
-        newDoc(NCLOC, 99_000d),
-        // 2 docs with ncloc>=100K and ncloc<500K
-        newDoc(NCLOC, 100_000d),
-        newDoc(NCLOC, 499_000d),
-        // 5 docs with ncloc>= 500K
-        newDoc(NCLOC, 500_000d),
-        newDoc(NCLOC, 100_000_000d),
-        newDoc(NCLOC, 500_000d),
-        newDoc(NCLOC, 1_000_000d),
-        newDoc(NCLOC, 100_000_000_000d));
+      // 3 docs with ncloc<1K
+      newDoc(NCLOC, 0d),
+      newDoc(NCLOC, 0d),
+      newDoc(NCLOC, 999d),
+      // 2 docs with ncloc>=1K and ncloc<10K
+      newDoc(NCLOC, 1_000d),
+      newDoc(NCLOC, 9_999d),
+      // 4 docs with ncloc>=10K and ncloc<100K
+      newDoc(NCLOC, 10_000d),
+      newDoc(NCLOC, 10_000d),
+      newDoc(NCLOC, 11_000d),
+      newDoc(NCLOC, 99_000d),
+      // 2 docs with ncloc>=100K and ncloc<500K
+      newDoc(NCLOC, 100_000d),
+      newDoc(NCLOC, 499_000d),
+      // 5 docs with ncloc>= 500K
+      newDoc(NCLOC, 500_000d),
+      newDoc(NCLOC, 100_000_000d),
+      newDoc(NCLOC, 500_000d),
+      newDoc(NCLOC, 1_000_000d),
+      newDoc(NCLOC, 100_000_000_000d));
 
     Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(NCLOC)).getFacets();
 
     assertThat(facets.get(NCLOC)).containsExactly(
-        entry("*-1000.0", 3L),
-        entry("1000.0-10000.0", 2L),
-        entry("10000.0-100000.0", 4L),
-        entry("100000.0-500000.0", 2L),
-        entry("500000.0-*", 5L));
+      entry("*-1000.0", 3L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 4L),
+      entry("100000.0-500000.0", 2L),
+      entry("500000.0-*", 5L));
   }
 
   @Test
   public void facet_ncloc_is_sticky() {
     index(
-        // 1 docs with ncloc<1K
-        newDoc(NCLOC, 999d, COVERAGE, 0d, DUPLICATION, 0d),
-        // 2 docs with ncloc>=1K and ncloc<10K
-        newDoc(NCLOC, 1_000d, COVERAGE, 10d, DUPLICATION, 0d),
-        newDoc(NCLOC, 9_999d, COVERAGE, 20d, DUPLICATION, 0d),
-        // 3 docs with ncloc>=10K and ncloc<100K
-        newDoc(NCLOC, 10_000d, COVERAGE, 31d, DUPLICATION, 0d),
-        newDoc(NCLOC, 11_000d, COVERAGE, 40d, DUPLICATION, 0d),
-        newDoc(NCLOC, 99_000d, COVERAGE, 50d, DUPLICATION, 0d),
-        // 2 docs with ncloc>=100K and ncloc<500K
-        newDoc(NCLOC, 100_000d, COVERAGE, 71d, DUPLICATION, 0d),
-        newDoc(NCLOC, 499_000d, COVERAGE, 80d, DUPLICATION, 0d),
-        // 1 docs with ncloc>= 500K
-        newDoc(NCLOC, 501_000d, COVERAGE, 81d, DUPLICATION, 20d));
+      // 1 docs with ncloc<1K
+      newDoc(NCLOC, 999d, COVERAGE, 0d, DUPLICATION, 0d),
+      // 2 docs with ncloc>=1K and ncloc<10K
+      newDoc(NCLOC, 1_000d, COVERAGE, 10d, DUPLICATION, 0d),
+      newDoc(NCLOC, 9_999d, COVERAGE, 20d, DUPLICATION, 0d),
+      // 3 docs with ncloc>=10K and ncloc<100K
+      newDoc(NCLOC, 10_000d, COVERAGE, 31d, DUPLICATION, 0d),
+      newDoc(NCLOC, 11_000d, COVERAGE, 40d, DUPLICATION, 0d),
+      newDoc(NCLOC, 99_000d, COVERAGE, 50d, DUPLICATION, 0d),
+      // 2 docs with ncloc>=100K and ncloc<500K
+      newDoc(NCLOC, 100_000d, COVERAGE, 71d, DUPLICATION, 0d),
+      newDoc(NCLOC, 499_000d, COVERAGE, 80d, DUPLICATION, 0d),
+      // 1 docs with ncloc>= 500K
+      newDoc(NCLOC, 501_000d, COVERAGE, 81d, DUPLICATION, 20d));
 
     Facets facets = underTest.search(new ProjectMeasuresQuery()
-            .addMetricCriterion(new MetricCriterion(NCLOC, Operator.LT, 10_000d))
-            .addMetricCriterion(new MetricCriterion(DUPLICATION, Operator.LT, 10d)),
-        new SearchOptions().addFacets(NCLOC, COVERAGE)).getFacets();
+      .addMetricCriterion(new MetricCriterion(NCLOC, Operator.LT, 10_000d))
+      .addMetricCriterion(new MetricCriterion(DUPLICATION, Operator.LT, 10d)),
+      new SearchOptions().addFacets(NCLOC, COVERAGE)).getFacets();
 
     // Sticky facet on ncloc does not take into account ncloc filter
     assertThat(facets.get(NCLOC)).containsExactly(
-        entry("*-1000.0", 1L),
-        entry("1000.0-10000.0", 2L),
-        entry("10000.0-100000.0", 3L),
-        entry("100000.0-500000.0", 2L),
-        entry("500000.0-*", 0L));
+      entry("*-1000.0", 1L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 3L),
+      entry("100000.0-500000.0", 2L),
+      entry("500000.0-*", 0L));
     // But facet on coverage does well take into into filters
     assertThat(facets.get(COVERAGE)).containsExactly(
-        entry("*-30.0", 3L),
-        entry("30.0-50.0", 0L),
-        entry("50.0-70.0", 0L),
-        entry("70.0-80.0", 0L),
-        entry("80.0-*", 0L));
+      entry("*-30.0", 3L),
+      entry("30.0-50.0", 0L),
+      entry("50.0-70.0", 0L),
+      entry("70.0-80.0", 0L),
+      entry("80.0-*", 0L));
   }
 
   @Test
   public void facet_ncloc_contains_only_projects_authorized_for_user() throws Exception {
     // User can see these projects
     indexForUser(USER1,
-        // docs with ncloc<1K
-        newDoc(NCLOC, 0d),
-        newDoc(NCLOC, 100d),
-        newDoc(NCLOC, 999d),
-        // docs with ncloc>=1K and ncloc<10K
-        newDoc(NCLOC, 1_000d),
-        newDoc(NCLOC, 9_999d));
+      // docs with ncloc<1K
+      newDoc(NCLOC, 0d),
+      newDoc(NCLOC, 100d),
+      newDoc(NCLOC, 999d),
+      // docs with ncloc>=1K and ncloc<10K
+      newDoc(NCLOC, 1_000d),
+      newDoc(NCLOC, 9_999d));
 
     // User cannot see these projects
     indexForUser(USER2,
-        // doc with ncloc>=10K and ncloc<100K
-        newDoc(NCLOC, 11_000d),
-        // doc with ncloc>=100K and ncloc<500K
-        newDoc(NCLOC, 499_000d),
-        // doc with ncloc>= 500K
-        newDoc(NCLOC, 501_000d));
+      // doc with ncloc>=10K and ncloc<100K
+      newDoc(NCLOC, 11_000d),
+      // doc with ncloc>=100K and ncloc<500K
+      newDoc(NCLOC, 499_000d),
+      // doc with ncloc>= 500K
+      newDoc(NCLOC, 501_000d));
 
     userSession.logIn(USER1);
     Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(NCLOC)).getFacets();
 
     assertThat(facets.get(NCLOC)).containsExactly(
-        entry("*-1000.0", 3L),
-        entry("1000.0-10000.0", 2L),
-        entry("10000.0-100000.0", 0L),
-        entry("100000.0-500000.0", 0L),
-        entry("500000.0-*", 0L));
+      entry("*-1000.0", 3L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 0L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
   }
 
   @Test
   public void facet_coverage() {
     index(
-        // 3 docs with coverage<30%
-        newDoc(COVERAGE, 0d),
-        newDoc(COVERAGE, 0d),
-        newDoc(COVERAGE, 29d),
-        // 2 docs with coverage>=30% and coverage<50%
-        newDoc(COVERAGE, 30d),
-        newDoc(COVERAGE, 49d),
-        // 4 docs with coverage>=50% and coverage<70%
-        newDoc(COVERAGE, 50d),
-        newDoc(COVERAGE, 60d),
-        newDoc(COVERAGE, 60d),
-        newDoc(COVERAGE, 69d),
-        // 2 docs with coverage>=70% and coverage<80%
-        newDoc(COVERAGE, 70d),
-        newDoc(COVERAGE, 79d),
-        // 5 docs with coverage>= 80%
-        newDoc(COVERAGE, 80d),
-        newDoc(COVERAGE, 80d),
-        newDoc(COVERAGE, 90d),
-        newDoc(COVERAGE, 90.5d),
-        newDoc(COVERAGE, 100d));
+      // 3 docs with coverage<30%
+      newDoc(COVERAGE, 0d),
+      newDoc(COVERAGE, 0d),
+      newDoc(COVERAGE, 29d),
+      // 2 docs with coverage>=30% and coverage<50%
+      newDoc(COVERAGE, 30d),
+      newDoc(COVERAGE, 49d),
+      // 4 docs with coverage>=50% and coverage<70%
+      newDoc(COVERAGE, 50d),
+      newDoc(COVERAGE, 60d),
+      newDoc(COVERAGE, 60d),
+      newDoc(COVERAGE, 69d),
+      // 2 docs with coverage>=70% and coverage<80%
+      newDoc(COVERAGE, 70d),
+      newDoc(COVERAGE, 79d),
+      // 5 docs with coverage>= 80%
+      newDoc(COVERAGE, 80d),
+      newDoc(COVERAGE, 80d),
+      newDoc(COVERAGE, 90d),
+      newDoc(COVERAGE, 90.5d),
+      newDoc(COVERAGE, 100d));
 
     Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(COVERAGE)).getFacets();
 
     assertThat(facets.get(COVERAGE)).containsExactly(
-        entry("*-30.0", 3L),
-        entry("30.0-50.0", 2L),
-        entry("50.0-70.0", 4L),
-        entry("70.0-80.0", 2L),
-        entry("80.0-*", 5L));
+      entry("*-30.0", 3L),
+      entry("30.0-50.0", 2L),
+      entry("50.0-70.0", 4L),
+      entry("70.0-80.0", 2L),
+      entry("80.0-*", 5L));
   }
 
   @Test
   public void facet_coverage_is_sticky() {
     index(
-        // docs with coverage<30%
-        newDoc(NCLOC, 999d, COVERAGE, 0d, DUPLICATION, 0d),
-        newDoc(NCLOC, 1_000d, COVERAGE, 10d, DUPLICATION, 0d),
-        newDoc(NCLOC, 9_999d, COVERAGE, 20d, DUPLICATION, 0d),
-        // docs with coverage>=30% and coverage<50%
-        newDoc(NCLOC, 10_000d, COVERAGE, 31d, DUPLICATION, 0d),
-        newDoc(NCLOC, 11_000d, COVERAGE, 40d, DUPLICATION, 0d),
-        // docs with coverage>=50% and coverage<70%
-        newDoc(NCLOC, 99_000d, COVERAGE, 50d, DUPLICATION, 0d),
-        // docs with coverage>=70% and coverage<80%
-        newDoc(NCLOC, 100_000d, COVERAGE, 71d, DUPLICATION, 0d),
-        // docs with coverage>= 80%
-        newDoc(NCLOC, 499_000d, COVERAGE, 80d, DUPLICATION, 15d),
-        newDoc(NCLOC, 501_000d, COVERAGE, 810d, DUPLICATION, 20d));
+      // docs with coverage<30%
+      newDoc(NCLOC, 999d, COVERAGE, 0d, DUPLICATION, 0d),
+      newDoc(NCLOC, 1_000d, COVERAGE, 10d, DUPLICATION, 0d),
+      newDoc(NCLOC, 9_999d, COVERAGE, 20d, DUPLICATION, 0d),
+      // docs with coverage>=30% and coverage<50%
+      newDoc(NCLOC, 10_000d, COVERAGE, 31d, DUPLICATION, 0d),
+      newDoc(NCLOC, 11_000d, COVERAGE, 40d, DUPLICATION, 0d),
+      // docs with coverage>=50% and coverage<70%
+      newDoc(NCLOC, 99_000d, COVERAGE, 50d, DUPLICATION, 0d),
+      // docs with coverage>=70% and coverage<80%
+      newDoc(NCLOC, 100_000d, COVERAGE, 71d, DUPLICATION, 0d),
+      // docs with coverage>= 80%
+      newDoc(NCLOC, 499_000d, COVERAGE, 80d, DUPLICATION, 15d),
+      newDoc(NCLOC, 501_000d, COVERAGE, 810d, DUPLICATION, 20d));
 
     Facets facets = underTest.search(new ProjectMeasuresQuery()
-            .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LT, 30d))
-            .addMetricCriterion(new MetricCriterion(DUPLICATION, Operator.LT, 10d)),
-        new SearchOptions().addFacets(COVERAGE, NCLOC)).getFacets();
+      .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LT, 30d))
+      .addMetricCriterion(new MetricCriterion(DUPLICATION, Operator.LT, 10d)),
+      new SearchOptions().addFacets(COVERAGE, NCLOC)).getFacets();
 
     // Sticky facet on coverage does not take into account coverage filter
     assertThat(facets.get(COVERAGE)).containsExactly(
-        entry("*-30.0", 3L),
-        entry("30.0-50.0", 2L),
-        entry("50.0-70.0", 1L),
-        entry("70.0-80.0", 1L),
-        entry("80.0-*", 0L));
+      entry("*-30.0", 3L),
+      entry("30.0-50.0", 2L),
+      entry("50.0-70.0", 1L),
+      entry("70.0-80.0", 1L),
+      entry("80.0-*", 0L));
     // But facet on ncloc does well take into into filters
     assertThat(facets.get(NCLOC)).containsExactly(
-        entry("*-1000.0", 1L),
-        entry("1000.0-10000.0", 2L),
-        entry("10000.0-100000.0", 0L),
-        entry("100000.0-500000.0", 0L),
-        entry("500000.0-*", 0L));
+      entry("*-1000.0", 1L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 0L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
   }
 
   @Test
   public void facet_coverage_contains_only_projects_authorized_for_user() throws Exception {
     // User can see these projects
     indexForUser(USER1,
-        // docs with coverage<30%
-        newDoc(COVERAGE, 0d),
-        newDoc(COVERAGE, 0d),
-        newDoc(COVERAGE, 29d),
-        // docs with coverage>=30% and coverage<50%
-        newDoc(COVERAGE, 30d),
-        newDoc(COVERAGE, 49d));
+      // docs with coverage<30%
+      newDoc(COVERAGE, 0d),
+      newDoc(COVERAGE, 0d),
+      newDoc(COVERAGE, 29d),
+      // docs with coverage>=30% and coverage<50%
+      newDoc(COVERAGE, 30d),
+      newDoc(COVERAGE, 49d));
 
     // User cannot see these projects
     indexForUser(USER2,
-        // docs with coverage>=50% and coverage<70%
-        newDoc(COVERAGE, 50d),
-        // docs with coverage>=70% and coverage<80%
-        newDoc(COVERAGE, 70d),
-        // docs with coverage>= 80%
-        newDoc(COVERAGE, 80d));
+      // docs with coverage>=50% and coverage<70%
+      newDoc(COVERAGE, 50d),
+      // docs with coverage>=70% and coverage<80%
+      newDoc(COVERAGE, 70d),
+      // docs with coverage>= 80%
+      newDoc(COVERAGE, 80d));
 
     userSession.logIn(USER1);
     Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(COVERAGE)).getFacets();
 
     assertThat(facets.get(COVERAGE)).containsExactly(
-        entry("*-30.0", 3L),
-        entry("30.0-50.0", 2L),
-        entry("50.0-70.0", 0L),
-        entry("70.0-80.0", 0L),
-        entry("80.0-*", 0L));
+      entry("*-30.0", 3L),
+      entry("30.0-50.0", 2L),
+      entry("50.0-70.0", 0L),
+      entry("70.0-80.0", 0L),
+      entry("80.0-*", 0L));
   }
 
   @Test
   public void facet_duplicated_lines_density() {
     index(
-        // 3 docs with duplication<3%
-        newDoc(DUPLICATION, 0d),
-        newDoc(DUPLICATION, 0d),
-        newDoc(DUPLICATION, 2.9d),
-        // 2 docs with duplication>=3% and duplication<5%
-        newDoc(DUPLICATION, 3d),
-        newDoc(DUPLICATION, 4.9d),
-        // 4 docs with duplication>=5% and duplication<10%
-        newDoc(DUPLICATION, 5d),
-        newDoc(DUPLICATION, 6d),
-        newDoc(DUPLICATION, 6d),
-        newDoc(DUPLICATION, 9.9d),
-        // 2 docs with duplication>=10% and duplication<20%
-        newDoc(DUPLICATION, 10d),
-        newDoc(DUPLICATION, 19.9d),
-        // 5 docs with duplication>= 20%
-        newDoc(DUPLICATION, 20d),
-        newDoc(DUPLICATION, 20d),
-        newDoc(DUPLICATION, 50d),
-        newDoc(DUPLICATION, 80d),
-        newDoc(DUPLICATION, 100d));
+      // 3 docs with duplication<3%
+      newDoc(DUPLICATION, 0d),
+      newDoc(DUPLICATION, 0d),
+      newDoc(DUPLICATION, 2.9d),
+      // 2 docs with duplication>=3% and duplication<5%
+      newDoc(DUPLICATION, 3d),
+      newDoc(DUPLICATION, 4.9d),
+      // 4 docs with duplication>=5% and duplication<10%
+      newDoc(DUPLICATION, 5d),
+      newDoc(DUPLICATION, 6d),
+      newDoc(DUPLICATION, 6d),
+      newDoc(DUPLICATION, 9.9d),
+      // 2 docs with duplication>=10% and duplication<20%
+      newDoc(DUPLICATION, 10d),
+      newDoc(DUPLICATION, 19.9d),
+      // 5 docs with duplication>= 20%
+      newDoc(DUPLICATION, 20d),
+      newDoc(DUPLICATION, 20d),
+      newDoc(DUPLICATION, 50d),
+      newDoc(DUPLICATION, 80d),
+      newDoc(DUPLICATION, 100d));
 
     Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(DUPLICATION)).getFacets();
 
     assertThat(facets.get(DUPLICATION)).containsExactly(
-        entry("*-3.0", 3L),
-        entry("3.0-5.0", 2L),
-        entry("5.0-10.0", 4L),
-        entry("10.0-20.0", 2L),
-        entry("20.0-*", 5L));
+      entry("*-3.0", 3L),
+      entry("3.0-5.0", 2L),
+      entry("5.0-10.0", 4L),
+      entry("10.0-20.0", 2L),
+      entry("20.0-*", 5L));
   }
 
   @Test
   public void facet_duplicated_lines_density_is_sticky() {
     index(
-        // docs with duplication<3%
-        newDoc(DUPLICATION, 0d, NCLOC, 999d, COVERAGE, 0d),
-        // docs with duplication>=3% and duplication<5%
-        newDoc(DUPLICATION, 3d, NCLOC, 5000d, COVERAGE, 0d),
-        newDoc(DUPLICATION, 4.9d, NCLOC, 6000d, COVERAGE, 0d),
-        // docs with duplication>=5% and duplication<10%
-        newDoc(DUPLICATION, 5d, NCLOC, 11000d, COVERAGE, 0d),
-        // docs with duplication>=10% and duplication<20%
-        newDoc(DUPLICATION, 10d, NCLOC, 120000d, COVERAGE, 10d),
-        newDoc(DUPLICATION, 19.9d, NCLOC, 130000d, COVERAGE, 20d),
-        // docs with duplication>= 20%
-        newDoc(DUPLICATION, 20d, NCLOC, 1000000d, COVERAGE, 40d));
+      // docs with duplication<3%
+      newDoc(DUPLICATION, 0d, NCLOC, 999d, COVERAGE, 0d),
+      // docs with duplication>=3% and duplication<5%
+      newDoc(DUPLICATION, 3d, NCLOC, 5000d, COVERAGE, 0d),
+      newDoc(DUPLICATION, 4.9d, NCLOC, 6000d, COVERAGE, 0d),
+      // docs with duplication>=5% and duplication<10%
+      newDoc(DUPLICATION, 5d, NCLOC, 11000d, COVERAGE, 0d),
+      // docs with duplication>=10% and duplication<20%
+      newDoc(DUPLICATION, 10d, NCLOC, 120000d, COVERAGE, 10d),
+      newDoc(DUPLICATION, 19.9d, NCLOC, 130000d, COVERAGE, 20d),
+      // docs with duplication>= 20%
+      newDoc(DUPLICATION, 20d, NCLOC, 1000000d, COVERAGE, 40d));
 
     Facets facets = underTest.search(new ProjectMeasuresQuery()
-            .addMetricCriterion(new MetricCriterion(DUPLICATION, Operator.LT, 10d))
-            .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LT, 30d)),
-        new SearchOptions().addFacets(DUPLICATION, NCLOC)).getFacets();
+      .addMetricCriterion(new MetricCriterion(DUPLICATION, Operator.LT, 10d))
+      .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LT, 30d)),
+      new SearchOptions().addFacets(DUPLICATION, NCLOC)).getFacets();
 
     // Sticky facet on duplication does not take into account duplication filter
     assertThat(facets.get(DUPLICATION)).containsExactly(
-        entry("*-3.0", 1L),
-        entry("3.0-5.0", 2L),
-        entry("5.0-10.0", 1L),
-        entry("10.0-20.0", 2L),
-        entry("20.0-*", 0L));
+      entry("*-3.0", 1L),
+      entry("3.0-5.0", 2L),
+      entry("5.0-10.0", 1L),
+      entry("10.0-20.0", 2L),
+      entry("20.0-*", 0L));
     // But facet on ncloc does well take into into filters
     assertThat(facets.get(NCLOC)).containsExactly(
-        entry("*-1000.0", 1L),
-        entry("1000.0-10000.0", 2L),
-        entry("10000.0-100000.0", 1L),
-        entry("100000.0-500000.0", 0L),
-        entry("500000.0-*", 0L));
+      entry("*-1000.0", 1L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 1L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
   }
 
   @Test
   public void facet_duplicated_lines_density_contains_only_projects_authorized_for_user() throws Exception {
     // User can see these projects
     indexForUser(USER1,
-        // docs with duplication<3%
-        newDoc(DUPLICATION, 0d),
-        newDoc(DUPLICATION, 0d),
-        newDoc(DUPLICATION, 2.9d),
-        // docs with duplication>=3% and duplication<5%
-        newDoc(DUPLICATION, 3d),
-        newDoc(DUPLICATION, 4.9d));
+      // docs with duplication<3%
+      newDoc(DUPLICATION, 0d),
+      newDoc(DUPLICATION, 0d),
+      newDoc(DUPLICATION, 2.9d),
+      // docs with duplication>=3% and duplication<5%
+      newDoc(DUPLICATION, 3d),
+      newDoc(DUPLICATION, 4.9d));
 
     // User cannot see these projects
     indexForUser(USER2,
-        // docs with duplication>=5% and duplication<10%
-        newDoc(DUPLICATION, 5d),
-        // docs with duplication>=10% and duplication<20%
-        newDoc(DUPLICATION, 10d),
-        // docs with duplication>= 20%
-        newDoc(DUPLICATION, 20d));
+      // docs with duplication>=5% and duplication<10%
+      newDoc(DUPLICATION, 5d),
+      // docs with duplication>=10% and duplication<20%
+      newDoc(DUPLICATION, 10d),
+      // docs with duplication>= 20%
+      newDoc(DUPLICATION, 20d));
 
     userSession.logIn(USER1);
     Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(DUPLICATION)).getFacets();
 
     assertThat(facets.get(DUPLICATION)).containsExactly(
-        entry("*-3.0", 3L),
-        entry("3.0-5.0", 2L),
-        entry("5.0-10.0", 0L),
-        entry("10.0-20.0", 0L),
-        entry("20.0-*", 0L));
+      entry("*-3.0", 3L),
+      entry("3.0-5.0", 2L),
+      entry("5.0-10.0", 0L),
+      entry("10.0-20.0", 0L),
+      entry("20.0-*", 0L));
   }
 
   @Test
   public void facet_maintainability_rating() {
     index(
-        // 3 docs with rating A
-        newDoc(MAINTAINABILITY_RATING, 1d),
-        newDoc(MAINTAINABILITY_RATING, 1d),
-        newDoc(MAINTAINABILITY_RATING, 1d),
-        // 2 docs with rating B
-        newDoc(MAINTAINABILITY_RATING, 2d),
-        newDoc(MAINTAINABILITY_RATING, 2d),
-        // 4 docs with rating C
-        newDoc(MAINTAINABILITY_RATING, 3d),
-        newDoc(MAINTAINABILITY_RATING, 3d),
-        newDoc(MAINTAINABILITY_RATING, 3d),
-        newDoc(MAINTAINABILITY_RATING, 3d),
-        // 2 docs with rating D
-        newDoc(MAINTAINABILITY_RATING, 4d),
-        newDoc(MAINTAINABILITY_RATING, 4d),
-        // 5 docs with rating E
-        newDoc(MAINTAINABILITY_RATING, 5d),
-        newDoc(MAINTAINABILITY_RATING, 5d),
-        newDoc(MAINTAINABILITY_RATING, 5d),
-        newDoc(MAINTAINABILITY_RATING, 5d),
-        newDoc(MAINTAINABILITY_RATING, 5d));
+      // 3 docs with rating A
+      newDoc(MAINTAINABILITY_RATING, 1d),
+      newDoc(MAINTAINABILITY_RATING, 1d),
+      newDoc(MAINTAINABILITY_RATING, 1d),
+      // 2 docs with rating B
+      newDoc(MAINTAINABILITY_RATING, 2d),
+      newDoc(MAINTAINABILITY_RATING, 2d),
+      // 4 docs with rating C
+      newDoc(MAINTAINABILITY_RATING, 3d),
+      newDoc(MAINTAINABILITY_RATING, 3d),
+      newDoc(MAINTAINABILITY_RATING, 3d),
+      newDoc(MAINTAINABILITY_RATING, 3d),
+      // 2 docs with rating D
+      newDoc(MAINTAINABILITY_RATING, 4d),
+      newDoc(MAINTAINABILITY_RATING, 4d),
+      // 5 docs with rating E
+      newDoc(MAINTAINABILITY_RATING, 5d),
+      newDoc(MAINTAINABILITY_RATING, 5d),
+      newDoc(MAINTAINABILITY_RATING, 5d),
+      newDoc(MAINTAINABILITY_RATING, 5d),
+      newDoc(MAINTAINABILITY_RATING, 5d));
 
     Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(MAINTAINABILITY_RATING)).getFacets();
 
     assertThat(facets.get(MAINTAINABILITY_RATING)).containsExactly(
-        entry("1", 3L),
-        entry("2", 2L),
-        entry("3", 4L),
-        entry("4", 2L),
-        entry("5", 5L));
+      entry("1", 3L),
+      entry("2", 2L),
+      entry("3", 4L),
+      entry("4", 2L),
+      entry("5", 5L));
   }
 
   @Test
   public void facet_maintainability_rating_is_sticky() {
     index(
-        // docs with rating A
-        newDoc(MAINTAINABILITY_RATING, 1d, NCLOC, 100d, COVERAGE, 0d),
-        newDoc(MAINTAINABILITY_RATING, 1d, NCLOC, 200d, COVERAGE, 0d),
-        newDoc(MAINTAINABILITY_RATING, 1d, NCLOC, 999d, COVERAGE, 0d),
-        // docs with rating B
-        newDoc(MAINTAINABILITY_RATING, 2d, NCLOC, 2000d, COVERAGE, 0d),
-        newDoc(MAINTAINABILITY_RATING, 2d, NCLOC, 5000d, COVERAGE, 0d),
-        // docs with rating C
-        newDoc(MAINTAINABILITY_RATING, 3d, NCLOC, 20000d, COVERAGE, 0d),
-        newDoc(MAINTAINABILITY_RATING, 3d, NCLOC, 30000d, COVERAGE, 0d),
-        newDoc(MAINTAINABILITY_RATING, 3d, NCLOC, 40000d, COVERAGE, 0d),
-        newDoc(MAINTAINABILITY_RATING, 3d, NCLOC, 50000d, COVERAGE, 0d),
-        // docs with rating D
-        newDoc(MAINTAINABILITY_RATING, 4d, NCLOC, 120000d, COVERAGE, 0d),
-        // docs with rating E
-        newDoc(MAINTAINABILITY_RATING, 5d, NCLOC, 600000d, COVERAGE, 40d),
-        newDoc(MAINTAINABILITY_RATING, 5d, NCLOC, 700000d, COVERAGE, 50d),
-        newDoc(MAINTAINABILITY_RATING, 5d, NCLOC, 800000d, COVERAGE, 60d));
+      // docs with rating A
+      newDoc(MAINTAINABILITY_RATING, 1d, NCLOC, 100d, COVERAGE, 0d),
+      newDoc(MAINTAINABILITY_RATING, 1d, NCLOC, 200d, COVERAGE, 0d),
+      newDoc(MAINTAINABILITY_RATING, 1d, NCLOC, 999d, COVERAGE, 0d),
+      // docs with rating B
+      newDoc(MAINTAINABILITY_RATING, 2d, NCLOC, 2000d, COVERAGE, 0d),
+      newDoc(MAINTAINABILITY_RATING, 2d, NCLOC, 5000d, COVERAGE, 0d),
+      // docs with rating C
+      newDoc(MAINTAINABILITY_RATING, 3d, NCLOC, 20000d, COVERAGE, 0d),
+      newDoc(MAINTAINABILITY_RATING, 3d, NCLOC, 30000d, COVERAGE, 0d),
+      newDoc(MAINTAINABILITY_RATING, 3d, NCLOC, 40000d, COVERAGE, 0d),
+      newDoc(MAINTAINABILITY_RATING, 3d, NCLOC, 50000d, COVERAGE, 0d),
+      // docs with rating D
+      newDoc(MAINTAINABILITY_RATING, 4d, NCLOC, 120000d, COVERAGE, 0d),
+      // docs with rating E
+      newDoc(MAINTAINABILITY_RATING, 5d, NCLOC, 600000d, COVERAGE, 40d),
+      newDoc(MAINTAINABILITY_RATING, 5d, NCLOC, 700000d, COVERAGE, 50d),
+      newDoc(MAINTAINABILITY_RATING, 5d, NCLOC, 800000d, COVERAGE, 60d));
 
     Facets facets = underTest.search(new ProjectMeasuresQuery()
-            .addMetricCriterion(new MetricCriterion(MAINTAINABILITY_RATING, Operator.LT, 3d))
-            .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LT, 30d)),
-        new SearchOptions().addFacets(MAINTAINABILITY_RATING, NCLOC)).getFacets();
+      .addMetricCriterion(new MetricCriterion(MAINTAINABILITY_RATING, Operator.LT, 3d))
+      .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LT, 30d)),
+      new SearchOptions().addFacets(MAINTAINABILITY_RATING, NCLOC)).getFacets();
 
     // Sticky facet on maintainability rating does not take into account maintainability rating filter
     assertThat(facets.get(MAINTAINABILITY_RATING)).containsExactly(
-        entry("1", 3L),
-        entry("2", 2L),
-        entry("3", 4L),
-        entry("4", 1L),
-        entry("5", 0L));
+      entry("1", 3L),
+      entry("2", 2L),
+      entry("3", 4L),
+      entry("4", 1L),
+      entry("5", 0L));
     // But facet on ncloc does well take into into filters
     assertThat(facets.get(NCLOC)).containsExactly(
-        entry("*-1000.0", 3L),
-        entry("1000.0-10000.0", 2L),
-        entry("10000.0-100000.0", 0L),
-        entry("100000.0-500000.0", 0L),
-        entry("500000.0-*", 0L));
+      entry("*-1000.0", 3L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 0L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
   }
 
   @Test
   public void facet_maintainability_rating_contains_only_projects_authorized_for_user() throws Exception {
     // User can see these projects
     indexForUser(USER1,
-        // 3 docs with rating A
-        newDoc(MAINTAINABILITY_RATING, 1d),
-        newDoc(MAINTAINABILITY_RATING, 1d),
-        newDoc(MAINTAINABILITY_RATING, 1d),
-        // 2 docs with rating B
-        newDoc(MAINTAINABILITY_RATING, 2d),
-        newDoc(MAINTAINABILITY_RATING, 2d));
+      // 3 docs with rating A
+      newDoc(MAINTAINABILITY_RATING, 1d),
+      newDoc(MAINTAINABILITY_RATING, 1d),
+      newDoc(MAINTAINABILITY_RATING, 1d),
+      // 2 docs with rating B
+      newDoc(MAINTAINABILITY_RATING, 2d),
+      newDoc(MAINTAINABILITY_RATING, 2d));
 
     // User cannot see these projects
     indexForUser(USER2,
-        // docs with rating C
-        newDoc(MAINTAINABILITY_RATING, 3d),
-        // docs with rating D
-        newDoc(MAINTAINABILITY_RATING, 4d),
-        // docs with rating E
-        newDoc(MAINTAINABILITY_RATING, 5d));
+      // docs with rating C
+      newDoc(MAINTAINABILITY_RATING, 3d),
+      // docs with rating D
+      newDoc(MAINTAINABILITY_RATING, 4d),
+      // docs with rating E
+      newDoc(MAINTAINABILITY_RATING, 5d));
 
     userSession.logIn(USER1);
     Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(MAINTAINABILITY_RATING)).getFacets();
 
     assertThat(facets.get(MAINTAINABILITY_RATING)).containsExactly(
-        entry("1", 3L),
-        entry("2", 2L),
-        entry("3", 0L),
-        entry("4", 0L),
-        entry("5", 0L));
+      entry("1", 3L),
+      entry("2", 2L),
+      entry("3", 0L),
+      entry("4", 0L),
+      entry("5", 0L));
   }
 
   @Test
   public void facet_reliability_rating() {
     index(
-        // 3 docs with rating A
-        newDoc(RELIABILITY_RATING, 1d),
-        newDoc(RELIABILITY_RATING, 1d),
-        newDoc(RELIABILITY_RATING, 1d),
-        // 2 docs with rating B
-        newDoc(RELIABILITY_RATING, 2d),
-        newDoc(RELIABILITY_RATING, 2d),
-        // 4 docs with rating C
-        newDoc(RELIABILITY_RATING, 3d),
-        newDoc(RELIABILITY_RATING, 3d),
-        newDoc(RELIABILITY_RATING, 3d),
-        newDoc(RELIABILITY_RATING, 3d),
-        // 2 docs with rating D
-        newDoc(RELIABILITY_RATING, 4d),
-        newDoc(RELIABILITY_RATING, 4d),
-        // 5 docs with rating E
-        newDoc(RELIABILITY_RATING, 5d),
-        newDoc(RELIABILITY_RATING, 5d),
-        newDoc(RELIABILITY_RATING, 5d),
-        newDoc(RELIABILITY_RATING, 5d),
-        newDoc(RELIABILITY_RATING, 5d));
+      // 3 docs with rating A
+      newDoc(RELIABILITY_RATING, 1d),
+      newDoc(RELIABILITY_RATING, 1d),
+      newDoc(RELIABILITY_RATING, 1d),
+      // 2 docs with rating B
+      newDoc(RELIABILITY_RATING, 2d),
+      newDoc(RELIABILITY_RATING, 2d),
+      // 4 docs with rating C
+      newDoc(RELIABILITY_RATING, 3d),
+      newDoc(RELIABILITY_RATING, 3d),
+      newDoc(RELIABILITY_RATING, 3d),
+      newDoc(RELIABILITY_RATING, 3d),
+      // 2 docs with rating D
+      newDoc(RELIABILITY_RATING, 4d),
+      newDoc(RELIABILITY_RATING, 4d),
+      // 5 docs with rating E
+      newDoc(RELIABILITY_RATING, 5d),
+      newDoc(RELIABILITY_RATING, 5d),
+      newDoc(RELIABILITY_RATING, 5d),
+      newDoc(RELIABILITY_RATING, 5d),
+      newDoc(RELIABILITY_RATING, 5d));
 
     Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(RELIABILITY_RATING)).getFacets();
 
     assertThat(facets.get(RELIABILITY_RATING)).containsExactly(
-        entry("1", 3L),
-        entry("2", 2L),
-        entry("3", 4L),
-        entry("4", 2L),
-        entry("5", 5L));
+      entry("1", 3L),
+      entry("2", 2L),
+      entry("3", 4L),
+      entry("4", 2L),
+      entry("5", 5L));
   }
 
   @Test
   public void facet_security_rating() {
     index(
-        // 3 docs with rating A
-        newDoc(SECURITY_RATING, 1.0d),
-        newDoc(SECURITY_RATING, 1.0d),
-        newDoc(SECURITY_RATING, 1.0d),
-        // 2 docs with rating B
-        newDoc(SECURITY_RATING, 2.0d),
-        newDoc(SECURITY_RATING, 2.0d),
-        // 4 docs with rating C
-        newDoc(SECURITY_RATING, 3.0d),
-        newDoc(SECURITY_RATING, 3.0d),
-        newDoc(SECURITY_RATING, 3.0d),
-        newDoc(SECURITY_RATING, 3.0d),
-        // 2 docs with rating D
-        newDoc(SECURITY_RATING, 4.0d),
-        newDoc(SECURITY_RATING, 4.0d),
-        // 5 docs with rating E
-        newDoc(SECURITY_RATING, 5.0d),
-        newDoc(SECURITY_RATING, 5.0d),
-        newDoc(SECURITY_RATING, 5.0d),
-        newDoc(SECURITY_RATING, 5.0d),
-        newDoc(SECURITY_RATING, 5.0d));
+      // 3 docs with rating A
+      newDoc(SECURITY_RATING, 1.0d),
+      newDoc(SECURITY_RATING, 1.0d),
+      newDoc(SECURITY_RATING, 1.0d),
+      // 2 docs with rating B
+      newDoc(SECURITY_RATING, 2.0d),
+      newDoc(SECURITY_RATING, 2.0d),
+      // 4 docs with rating C
+      newDoc(SECURITY_RATING, 3.0d),
+      newDoc(SECURITY_RATING, 3.0d),
+      newDoc(SECURITY_RATING, 3.0d),
+      newDoc(SECURITY_RATING, 3.0d),
+      // 2 docs with rating D
+      newDoc(SECURITY_RATING, 4.0d),
+      newDoc(SECURITY_RATING, 4.0d),
+      // 5 docs with rating E
+      newDoc(SECURITY_RATING, 5.0d),
+      newDoc(SECURITY_RATING, 5.0d),
+      newDoc(SECURITY_RATING, 5.0d),
+      newDoc(SECURITY_RATING, 5.0d),
+      newDoc(SECURITY_RATING, 5.0d));
 
     Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(SECURITY_RATING)).getFacets();
 
     assertThat(facets.get(SECURITY_RATING)).containsExactly(
-        entry("1", 3L),
-        entry("2", 2L),
-        entry("3", 4L),
-        entry("4", 2L),
-        entry("5", 5L));
+      entry("1", 3L),
+      entry("2", 2L),
+      entry("3", 4L),
+      entry("4", 2L),
+      entry("5", 5L));
   }
 
   @Test
   public void facet_quality_gate() {
     index(
-        // 2 docs with QG OK
-        newDoc().setQualityGate(OK.name()),
-        newDoc().setQualityGate(OK.name()),
-        // 3 docs with QG WARN
-        newDoc().setQualityGate(WARN.name()),
-        newDoc().setQualityGate(WARN.name()),
-        newDoc().setQualityGate(WARN.name()),
-        // 4 docs with QG ERROR
-        newDoc().setQualityGate(ERROR.name()),
-        newDoc().setQualityGate(ERROR.name()),
-        newDoc().setQualityGate(ERROR.name()),
-        newDoc().setQualityGate(ERROR.name()));
+      // 2 docs with QG OK
+      newDoc().setQualityGate(OK.name()),
+      newDoc().setQualityGate(OK.name()),
+      // 3 docs with QG WARN
+      newDoc().setQualityGate(WARN.name()),
+      newDoc().setQualityGate(WARN.name()),
+      newDoc().setQualityGate(WARN.name()),
+      // 4 docs with QG ERROR
+      newDoc().setQualityGate(ERROR.name()),
+      newDoc().setQualityGate(ERROR.name()),
+      newDoc().setQualityGate(ERROR.name()),
+      newDoc().setQualityGate(ERROR.name()));
 
     LinkedHashMap<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(ALERT_STATUS_KEY)).getFacets().get(ALERT_STATUS_KEY);
 
     assertThat(result).containsExactly(
-        entry(ERROR.name(), 4L),
-        entry(WARN.name(), 3L),
-        entry(OK.name(), 2L));
+      entry(ERROR.name(), 4L),
+      entry(WARN.name(), 3L),
+      entry(OK.name(), 2L));
   }
 
   @Test
   public void facet_quality_gate_is_sticky() {
     index(
-        // 2 docs with QG OK
-        newDoc(NCLOC, 10d, COVERAGE, 0d).setQualityGate(OK.name()),
-        newDoc(NCLOC, 10d, COVERAGE, 0d).setQualityGate(OK.name()),
-        // 3 docs with QG WARN
-        newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGate(WARN.name()),
-        newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGate(WARN.name()),
-        newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGate(WARN.name()),
-        // 4 docs with QG ERROR
-        newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGate(ERROR.name()),
-        newDoc(NCLOC, 5000d, COVERAGE, 40d).setQualityGate(ERROR.name()),
-        newDoc(NCLOC, 12000d, COVERAGE, 50d).setQualityGate(ERROR.name()),
-        newDoc(NCLOC, 13000d, COVERAGE, 60d).setQualityGate(ERROR.name()));
+      // 2 docs with QG OK
+      newDoc(NCLOC, 10d, COVERAGE, 0d).setQualityGate(OK.name()),
+      newDoc(NCLOC, 10d, COVERAGE, 0d).setQualityGate(OK.name()),
+      // 3 docs with QG WARN
+      newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGate(WARN.name()),
+      newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGate(WARN.name()),
+      newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGate(WARN.name()),
+      // 4 docs with QG ERROR
+      newDoc(NCLOC, 100d, COVERAGE, 0d).setQualityGate(ERROR.name()),
+      newDoc(NCLOC, 5000d, COVERAGE, 40d).setQualityGate(ERROR.name()),
+      newDoc(NCLOC, 12000d, COVERAGE, 50d).setQualityGate(ERROR.name()),
+      newDoc(NCLOC, 13000d, COVERAGE, 60d).setQualityGate(ERROR.name()));
 
     Facets facets = underTest.search(new ProjectMeasuresQuery()
-            .setQualityGateStatus(ERROR)
-            .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LT, 55d)),
-        new SearchOptions().addFacets(ALERT_STATUS_KEY, NCLOC)).getFacets();
+      .setQualityGateStatus(ERROR)
+      .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LT, 55d)),
+      new SearchOptions().addFacets(ALERT_STATUS_KEY, NCLOC)).getFacets();
 
     // Sticky facet on quality gate does not take into account quality gate filter
     assertThat(facets.get(ALERT_STATUS_KEY)).containsOnly(
-        entry(OK.name(), 2L),
-        entry(WARN.name(), 3L),
-        entry(ERROR.name(), 3L));
+      entry(OK.name(), 2L),
+      entry(WARN.name(), 3L),
+      entry(ERROR.name(), 3L));
     // But facet on ncloc does well take into into filters
     assertThat(facets.get(NCLOC)).containsExactly(
-        entry("*-1000.0", 1L),
-        entry("1000.0-10000.0", 1L),
-        entry("10000.0-100000.0", 1L),
-        entry("100000.0-500000.0", 0L),
-        entry("500000.0-*", 0L));
+      entry("*-1000.0", 1L),
+      entry("1000.0-10000.0", 1L),
+      entry("10000.0-100000.0", 1L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
   }
 
   @Test
   public void facet_quality_gate_contains_only_projects_authorized_for_user() throws Exception {
     // User can see these projects
     indexForUser(USER1,
-        // 2 docs with QG OK
-        newDoc().setQualityGate(OK.name()),
-        newDoc().setQualityGate(OK.name()),
-        // 3 docs with QG WARN
-        newDoc().setQualityGate(WARN.name()),
-        newDoc().setQualityGate(WARN.name()),
-        newDoc().setQualityGate(WARN.name()));
+      // 2 docs with QG OK
+      newDoc().setQualityGate(OK.name()),
+      newDoc().setQualityGate(OK.name()),
+      // 3 docs with QG WARN
+      newDoc().setQualityGate(WARN.name()),
+      newDoc().setQualityGate(WARN.name()),
+      newDoc().setQualityGate(WARN.name()));
 
     // User cannot see these projects
     indexForUser(USER2,
-        // 4 docs with QG ERROR
-        newDoc().setQualityGate(ERROR.name()),
-        newDoc().setQualityGate(ERROR.name()),
-        newDoc().setQualityGate(ERROR.name()),
-        newDoc().setQualityGate(ERROR.name()));
+      // 4 docs with QG ERROR
+      newDoc().setQualityGate(ERROR.name()),
+      newDoc().setQualityGate(ERROR.name()),
+      newDoc().setQualityGate(ERROR.name()),
+      newDoc().setQualityGate(ERROR.name()));
 
     userSession.logIn(USER1);
     LinkedHashMap<String, Long> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(ALERT_STATUS_KEY)).getFacets().get(ALERT_STATUS_KEY);
 
     assertThat(result).containsExactly(
-        entry(ERROR.name(), 0L),
-        entry(WARN.name(), 3L),
-        entry(OK.name(), 2L));
+      entry(ERROR.name(), 0L),
+      entry(WARN.name(), 3L),
+      entry(OK.name(), 2L));
   }
 
   private void index(ProjectMeasuresDoc... docs) {
index 5b5efb1028d7515c5ce3e7c3cb14833b708d1fe4..fd382d321a6d5aa588e6cb8de2dc1c1e954a5161 100644 (file)
@@ -110,6 +110,8 @@ public class ComponentsService extends BaseService {
       .setParam(PARAM_ORGANIZATION, request.getOrganization())
       .setParam(PARAM_FILTER, request.getFilter())
       .setParam(Param.FACETS, request.getFacets())
+      .setParam(Param.SORT, request.getSort())
+      .setParam(Param.ASCENDING, request.getAsc())
       .setParam(Param.PAGE, request.getPage())
       .setParam(Param.PAGE_SIZE, request.getPageSize());
     return call(get, SearchProjectsWsResponse.parser());
index 05c3604ea273ba88c8f0147712672106abd012d6..f6d9e359645412c5222b00f6cf5c1e6fa6203860 100644 (file)
@@ -28,6 +28,7 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
 
 public class SearchProjectsRequest {
+
   public static final int MAX_PAGE_SIZE = 500;
   public static final int DEFAULT_PAGE_SIZE = 100;
 
@@ -36,6 +37,8 @@ public class SearchProjectsRequest {
   private final String organization;
   private final String filter;
   private final List<String> facets;
+  private final String sort;
+  private final Boolean asc;
 
   private SearchProjectsRequest(Builder builder) {
     this.page = builder.page;
@@ -43,6 +46,8 @@ public class SearchProjectsRequest {
     this.organization = builder.organization;
     this.filter = builder.filter;
     this.facets = builder.facets;
+    this.sort = builder.sort;
+    this.asc = builder.asc;
   }
 
   @CheckForNull
@@ -59,6 +64,11 @@ public class SearchProjectsRequest {
     return facets;
   }
 
+  @CheckForNull
+  public String getSort() {
+    return sort;
+  }
+
   public int getPageSize() {
     return pageSize;
   }
@@ -67,6 +77,11 @@ public class SearchProjectsRequest {
     return page;
   }
 
+  @CheckForNull
+  public Boolean getAsc() {
+    return asc;
+  }
+
   public static Builder builder() {
     return new Builder();
   }
@@ -77,6 +92,8 @@ public class SearchProjectsRequest {
     private Integer pageSize;
     private String filter;
     private List<String> facets = new ArrayList<>();
+    private String sort;
+    private Boolean asc;
 
     private Builder() {
       // enforce static factory method
@@ -107,6 +124,16 @@ public class SearchProjectsRequest {
       return this;
     }
 
+    public Builder setSort(@Nullable String sort) {
+      this.sort = sort;
+      return this;
+    }
+
+    public Builder setAsc(boolean asc) {
+      this.asc = asc;
+      return this;
+    }
+
     public SearchProjectsRequest build() {
       if (page == null) {
         page = 1;
index 1dbed44fe923ee187ab684546c61306e332ecb1c..7d7dcc05323cfe933f3c25fea5c850e50e02ce9f 100644 (file)
@@ -33,11 +33,12 @@ public class SearchWsRequest {
   private String query;
   private String language;
 
+  @CheckForNull
   public String getOrganization() {
     return organization;
   }
 
-  public SearchWsRequest setOrganization(String organization) {
+  public SearchWsRequest setOrganization(@Nullable String organization) {
     this.organization = organization;
     return this;
   }
index a2a71f91459a66b5acdf71760b358b7f5408d58e..2989b4f883ac3ca303e4d0ec3e43cddf7101527a 100644 (file)
@@ -21,12 +21,16 @@ package org.sonarqube.ws.client.component;
 
 import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.server.ws.WebService.Param;
 import org.sonarqube.ws.client.ServiceTester;
 import org.sonarqube.ws.client.WsConnector;
 
 import static java.util.Collections.singletonList;
 import static org.mockito.Mockito.mock;
+import static org.sonar.api.server.ws.WebService.Param.ASCENDING;
+import static org.sonar.api.server.ws.WebService.Param.FACETS;
+import static org.sonar.api.server.ws.WebService.Param.PAGE;
+import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
+import static org.sonar.api.server.ws.WebService.Param.SORT;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FILTER;
 
 public class ComponentsServiceTest {
@@ -41,6 +45,8 @@ public class ComponentsServiceTest {
     underTest.searchProjects(SearchProjectsRequest.builder()
       .setFilter("ncloc > 10")
       .setFacets(singletonList("ncloc"))
+      .setSort("coverage")
+      .setAsc(true)
       .setPage(3)
       .setPageSize(10)
       .build());
@@ -48,9 +54,29 @@ public class ComponentsServiceTest {
     serviceTester.assertThat(serviceTester.getGetRequest())
       .hasPath("search_projects")
       .hasParam(PARAM_FILTER, "ncloc > 10")
-      .hasParam(Param.FACETS, singletonList("ncloc"))
-      .hasParam(Param.PAGE, 3)
-      .hasParam(Param.PAGE_SIZE, 10)
+      .hasParam(FACETS, singletonList("ncloc"))
+      .hasParam(SORT, "coverage")
+      .hasParam(ASCENDING, true)
+      .hasParam(PAGE, 3)
+      .hasParam(PAGE_SIZE, 10)
+      .andNoOtherParam();
+  }
+
+  @Test
+  public void search_projects_without_sort() {
+    underTest.searchProjects(SearchProjectsRequest.builder()
+      .setFilter("ncloc > 10")
+      .setFacets(singletonList("ncloc"))
+      .setPage(3)
+      .setPageSize(10)
+      .build());
+
+    serviceTester.assertThat(serviceTester.getGetRequest())
+      .hasPath("search_projects")
+      .hasParam(PARAM_FILTER, "ncloc > 10")
+      .hasParam(FACETS, singletonList("ncloc"))
+      .hasParam(PAGE, 3)
+      .hasParam(PAGE_SIZE, 10)
       .andNoOtherParam();
   }