]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7282 rename .component.es to .measure.index
authorDaniel Schwarz <daniel.schwarz@sonarsource.com>
Wed, 11 Jan 2017 12:25:10 +0000 (13:25 +0100)
committerDaniel Schwarz <daniel.schwarz@sonarsource.com>
Mon, 16 Jan 2017 15:13:33 +0000 (16:13 +0100)
* All other elastic search indexes reside in
  "index" subpackages.
* We want to introduce a new component index
  and need the .component.index-package name
  for that one.

48 files changed:
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java
server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java
server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresDoc.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresIndex.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresIndexDefinition.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresIndexer.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresQuery.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectsEsModule.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/component/es/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactory.java
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/computation/task/projectanalysis/step/IndexProjectMeasuresStep.java
server/sonar-server/src/main/java/org/sonar/server/es/IndexerStartupTask.java
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresDoc.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexDefinition.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectsEsModule.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/index/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java
server/sonar-server/src/main/java/org/sonar/server/platform/BackendCleanup.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/test/java/org/sonar/server/component/ComponentCleanerServiceTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java
server/sonar-server/src/test/java/org/sonar/server/component/DefaultRubyComponentServiceTest.java
server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresIndexTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresIndexerTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresQueryTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectsEsModuleTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/component/ws/BulkUpdateKeyActionTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/IndexProjectMeasuresStepTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexerTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresQueryTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectsEsModuleTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/permission/index/PermissionIndexerTest.java
server/sonar-server/src/test/java/org/sonar/server/permission/index/PermissionIndexerTester.java
server/sonar-server/src/test/java/org/sonar/server/permission/ws/template/ApplyTemplateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/BackendCleanupTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkDeleteActionTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/AddProjectActionTest.java

index 34bf314e848aa82e8ac685a002b3beae214d580c..4a04422918d2b3fac544d6c46cf71c835ffbc7e6 100644 (file)
@@ -68,7 +68,6 @@ import org.sonar.process.logging.LogbackHelper;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.component.ComponentService;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
 import org.sonar.server.computation.queue.PurgeCeActivities;
 import org.sonar.server.computation.task.projectanalysis.ProjectAnalysisTaskModule;
 import org.sonar.server.computation.taskprocessor.CeTaskProcessorModule;
@@ -88,6 +87,7 @@ import org.sonar.server.issue.notification.NewIssuesNotificationDispatcher;
 import org.sonar.server.issue.notification.NewIssuesNotificationFactory;
 import org.sonar.server.issue.workflow.FunctionExecutor;
 import org.sonar.server.issue.workflow.IssueWorkflow;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.metric.CoreCustomMetrics;
 import org.sonar.server.metric.DefaultMetricFinder;
 import org.sonar.server.notification.DefaultNotificationManager;
index b74f12283570d66e7c158a6e9eeb3cde2bd7a8d8..0d67e6bec988890eb648d26c3421a33a837935eb 100644 (file)
@@ -29,8 +29,8 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.MyBatis;
 import org.sonar.db.component.ComponentDto;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
 import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.test.index.TestIndexer;
 
 @ServerSide
index 760a2726b2da6ea868aa45b608eca3d7f0047706..a2b02dc7a6c51e7b6186c9b3fe72472f3cb2d10d 100644 (file)
@@ -42,9 +42,9 @@ import org.sonar.core.util.Uuids;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.user.UserSession;
 
 import static com.google.common.collect.Lists.newArrayList;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresDoc.java b/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresDoc.java
deleted file mode 100644 (file)
index 357c271..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.es;
-
-import com.google.common.collect.ImmutableMap;
-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.core.util.stream.Collectors;
-import org.sonar.server.es.BaseDoc;
-
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
-
-public class ProjectMeasuresDoc extends BaseDoc {
-
-  public ProjectMeasuresDoc() {
-    super(new HashMap<>(4));
-  }
-
-  @Override
-  public String getId() {
-    return getField("_id");
-  }
-
-  @Override
-  public String getRouting() {
-    return getId();
-  }
-
-  @Override
-  public String getParent() {
-    return getId();
-  }
-
-  public ProjectMeasuresDoc setId(String s) {
-    setField("_id", s);
-    return this;
-  }
-
-  public String getKey() {
-    return getField(ProjectMeasuresIndexDefinition.FIELD_KEY);
-  }
-
-  public ProjectMeasuresDoc setKey(String s) {
-    setField(ProjectMeasuresIndexDefinition.FIELD_KEY, s);
-    return this;
-  }
-
-  public String getName() {
-    return getField(ProjectMeasuresIndexDefinition.FIELD_NAME);
-  }
-
-  public ProjectMeasuresDoc setName(String s) {
-    setField(ProjectMeasuresIndexDefinition.FIELD_NAME, s);
-    return this;
-  }
-
-  @CheckForNull
-  public Date getAnalysedAt() {
-    return getNullableField(ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT);
-  }
-
-  public ProjectMeasuresDoc setAnalysedAt(@Nullable Date d) {
-    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;
-  }
-
-  public ProjectMeasuresDoc setMeasuresFromMap(Map<String, Double> measures) {
-    setMeasures(
-      measures.entrySet().stream()
-        .map(entry -> ImmutableMap.<String, Object>of(
-          ProjectMeasuresIndexDefinition.FIELD_MEASURES_KEY, entry.getKey(),
-          ProjectMeasuresIndexDefinition.FIELD_MEASURES_VALUE, entry.getValue()))
-        .collect(Collectors.toList()));
-    return this;
-  }
-
-  @CheckForNull
-  public String getQualityGate() {
-    return getField(ProjectMeasuresIndexDefinition.FIELD_QUALITY_GATE);
-  }
-
-  public ProjectMeasuresDoc setQualityGate(@Nullable String s) {
-    setField(ProjectMeasuresIndexDefinition.FIELD_QUALITY_GATE, s);
-    return this;
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresIndex.java b/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresIndex.java
deleted file mode 100644 (file)
index 83ced73..0000000
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.es;
-
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Multimap;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.IntStream;
-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.aggregations.AggregationBuilder;
-import org.elasticsearch.search.aggregations.AggregationBuilders;
-import org.elasticsearch.search.aggregations.bucket.range.RangeBuilder;
-import org.elasticsearch.search.sort.SortOrder;
-import org.sonar.api.measures.Metric;
-import org.sonar.server.component.es.ProjectMeasuresQuery.MetricCriterion;
-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 org.sonar.server.es.StickyFacetBuilder;
-import org.sonar.server.user.UserSession;
-
-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.elasticsearch.index.query.QueryBuilders.termsQuery;
-import static org.elasticsearch.search.aggregations.AggregationBuilders.filters;
-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;
-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.component.es.ProjectMeasuresIndexDefinition.FIELD_AUTHORIZATION_GROUPS;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_AUTHORIZATION_USERS;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES_KEY;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES_VALUE;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_NAME;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_QUALITY_GATE;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.TYPE_AUTHORIZATION;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
-
-public class ProjectMeasuresIndex extends BaseIndex {
-
-  public static final List<String> SUPPORTED_FACETS = ImmutableList.of(
-    NCLOC_KEY,
-    DUPLICATED_LINES_DENSITY_KEY,
-    COVERAGE_KEY,
-    SQALE_RATING_KEY,
-    RELIABILITY_RATING_KEY,
-    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 final UserSession userSession;
-
-  public ProjectMeasuresIndex(EsClient client, UserSession userSession) {
-    super(client);
-    this.userSession = userSession;
-  }
-
-  public SearchIdResult<String> search(ProjectMeasuresQuery query, SearchOptions searchOptions) {
-    SearchRequestBuilder requestBuilder = getClient()
-      .prepareSearch(INDEX_PROJECT_MEASURES)
-      .setTypes(TYPE_PROJECT_MEASURES)
-      .setFetchSource(false)
-      .setFrom(searchOptions.getOffset())
-      .setSize(searchOptions.getLimit())
-      .addSort(FIELD_NAME + "." + SORT_SUFFIX, SortOrder.ASC);
-
-    BoolQueryBuilder esFilter = boolQuery();
-    Map<String, QueryBuilder> filters = createFilters(query);
-    filters.values().forEach(esFilter::must);
-    requestBuilder.setQuery(esFilter);
-
-    addFacets(requestBuilder, searchOptions, filters);
-    return new SearchIdResult<>(requestBuilder.get(), id -> id);
-  }
-
-  private static void addFacets(SearchRequestBuilder esSearch, SearchOptions options, Map<String, QueryBuilder> filters) {
-    if (!options.getFacets().isEmpty()) {
-      if (options.getFacets().contains(NCLOC_KEY)) {
-        addRangeFacet(esSearch, NCLOC_KEY, ImmutableList.of(1_000d, 10_000d, 100_000d, 500_000d), filters);
-      }
-      if (options.getFacets().contains(DUPLICATED_LINES_DENSITY_KEY)) {
-        addRangeFacet(esSearch, DUPLICATED_LINES_DENSITY_KEY, ImmutableList.of(3d, 5d, 10d, 20d), filters);
-      }
-      if (options.getFacets().contains(COVERAGE_KEY)) {
-        addRangeFacet(esSearch, COVERAGE_KEY, ImmutableList.of(30d, 50d, 70d, 80d), filters);
-      }
-      if (options.getFacets().contains(SQALE_RATING_KEY)) {
-        addRatingFacet(esSearch, SQALE_RATING_KEY, filters);
-      }
-      if (options.getFacets().contains(RELIABILITY_RATING_KEY)) {
-        addRatingFacet(esSearch, RELIABILITY_RATING_KEY, filters);
-      }
-      if (options.getFacets().contains(SECURITY_RATING_KEY)) {
-        addRatingFacet(esSearch, SECURITY_RATING_KEY, filters);
-      }
-      if (options.getFacets().contains(ALERT_STATUS_KEY)) {
-        esSearch.addAggregation(createStickyFacet(ALERT_STATUS_KEY, filters, createQualityGateFacet()));
-      }
-    }
-  }
-
-  private static void addRangeFacet(SearchRequestBuilder esSearch, String metricKey, List<Double> thresholds, Map<String, QueryBuilder> filters) {
-    esSearch.addAggregation(createStickyFacet(metricKey, filters, createRangeFacet(metricKey, thresholds)));
-  }
-
-  private static void addRatingFacet(SearchRequestBuilder esSearch, String metricKey, Map<String, QueryBuilder> filters) {
-    esSearch.addAggregation(createStickyFacet(metricKey, filters, createRatingFacet(metricKey)));
-  }
-
-  private static AggregationBuilder createStickyFacet(String metricKey, Map<String, QueryBuilder> filters, AggregationBuilder aggregationBuilder) {
-    StickyFacetBuilder facetBuilder = new StickyFacetBuilder(matchAllQuery(), filters);
-    BoolQueryBuilder facetFilter = facetBuilder.getStickyFacetFilter(metricKey);
-    return AggregationBuilders
-      .global(metricKey)
-      .subAggregation(AggregationBuilders.filter("facet_filter_" + metricKey)
-        .filter(facetFilter)
-        .subAggregation(aggregationBuilder));
-  }
-
-  private static AggregationBuilder createRangeFacet(String metricKey, List<Double> thresholds) {
-    RangeBuilder rangeAgg = AggregationBuilders.range(metricKey)
-      .field(FIELD_VALUE);
-    final int lastIndex = thresholds.size() - 1;
-    IntStream.range(0, thresholds.size())
-      .forEach(i -> {
-        if (i == 0) {
-          rangeAgg.addUnboundedTo(thresholds.get(0));
-          rangeAgg.addRange(thresholds.get(0), thresholds.get(1));
-        } else if (i == lastIndex) {
-          rangeAgg.addUnboundedFrom(thresholds.get(lastIndex));
-        } else {
-          rangeAgg.addRange(thresholds.get(i), thresholds.get(i + 1));
-        }
-      });
-
-    return AggregationBuilders.nested("nested_" + metricKey)
-      .path(FIELD_MEASURES)
-      .subAggregation(
-        AggregationBuilders.filter("filter_" + metricKey)
-          .filter(termsQuery(FIELD_KEY, metricKey))
-          .subAggregation(rangeAgg));
-  }
-
-  private static AggregationBuilder createRatingFacet(String metricKey) {
-    return AggregationBuilders.nested("nested_" + metricKey)
-      .path(FIELD_MEASURES)
-      .subAggregation(
-        AggregationBuilders.filter("filter_" + metricKey)
-          .filter(termsQuery(FIELD_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))));
-  }
-
-  private static AggregationBuilder createQualityGateFacet() {
-    return AggregationBuilders.filters(ALERT_STATUS_KEY)
-      .filter(Metric.Level.ERROR.name(), termQuery(FIELD_QUALITY_GATE, Metric.Level.ERROR.name()))
-      .filter(Metric.Level.WARN.name(), termQuery(FIELD_QUALITY_GATE, Metric.Level.WARN.name()))
-      .filter(Metric.Level.OK.name(), termQuery(FIELD_QUALITY_GATE, Metric.Level.OK.name()));
-  }
-
-  private Map<String, QueryBuilder> createFilters(ProjectMeasuresQuery query) {
-    Map<String, QueryBuilder> filters = new HashMap<>();
-    filters.put("__authorization", createAuthorizationFilter());
-    Multimap<String, MetricCriterion> metricCriterionMultimap = ArrayListMultimap.create();
-    query.getMetricCriteria().forEach(metricCriterion -> metricCriterionMultimap.put(metricCriterion.getMetricKey(), metricCriterion));
-    metricCriterionMultimap.asMap().entrySet().forEach(entry -> {
-      BoolQueryBuilder metricFilters = boolQuery();
-      entry.getValue()
-        .stream()
-        .map(criterion -> nestedQuery(FIELD_MEASURES, boolQuery()
-          .filter(termQuery(FIELD_KEY, criterion.getMetricKey()))
-          .filter(toValueQuery(criterion))))
-        .forEach(metricFilters::must);
-      filters.put(entry.getKey(), metricFilters);
-
-    });
-    if (query.hasQualityGateStatus()) {
-      filters.put(ALERT_STATUS_KEY, termQuery(FIELD_QUALITY_GATE, query.getQualityGateStatus().name()));
-    }
-    if (query.doesFilterOnProjectUuids()) {
-      filters.put("ids", termsQuery("_id", query.getProjectUuids()));
-    }
-    return filters;
-  }
-
-  private static QueryBuilder toValueQuery(MetricCriterion criterion) {
-    String fieldName = FIELD_VALUE;
-
-    switch (criterion.getOperator()) {
-      case GT:
-        return rangeQuery(fieldName).gt(criterion.getValue());
-      case GTE:
-        return rangeQuery(fieldName).gte(criterion.getValue());
-      case LT:
-        return rangeQuery(fieldName).lt(criterion.getValue());
-      case LTE:
-        return rangeQuery(fieldName).lte(criterion.getValue());
-      case EQ:
-        return termQuery(fieldName, criterion.getValue());
-      default:
-        throw new IllegalStateException("Metric criteria non supported: " + criterion.getOperator().name());
-    }
-  }
-
-  private QueryBuilder createAuthorizationFilter() {
-    Integer userLogin = userSession.getUserId();
-    Set<String> userGroupNames = userSession.getUserGroups();
-    BoolQueryBuilder groupsAndUser = boolQuery();
-    if (userLogin != null) {
-      groupsAndUser.should(termQuery(FIELD_AUTHORIZATION_USERS, userLogin.longValue()));
-    }
-    for (String group : userGroupNames) {
-      groupsAndUser.should(termQuery(FIELD_AUTHORIZATION_GROUPS, group));
-    }
-    return QueryBuilders.hasParentQuery(TYPE_AUTHORIZATION,
-      QueryBuilders.boolQuery().must(matchAllQuery()).filter(groupsAndUser));
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresIndexDefinition.java b/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresIndexDefinition.java
deleted file mode 100644 (file)
index 7424fb1..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.es;
-
-import com.google.common.collect.ImmutableMap;
-import org.sonar.api.config.Settings;
-import org.sonar.server.es.IndexDefinition;
-import org.sonar.server.es.NewIndex;
-
-public class ProjectMeasuresIndexDefinition implements IndexDefinition {
-
-  public static final String INDEX_PROJECT_MEASURES = "projectmeasures";
-
-  public static final String TYPE_PROJECT_MEASURES = "projectmeasures";
-  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_QUALITY_GATE = "qualityGate";
-  public static final String FIELD_MEASURES = "measures";
-  public static final String FIELD_MEASURES_KEY = "key";
-  public static final String FIELD_MEASURES_VALUE = "value";
-
-  public static final String TYPE_AUTHORIZATION = "authorization";
-  public static final String FIELD_AUTHORIZATION_PROJECT_UUID = "project";
-  public static final String FIELD_AUTHORIZATION_GROUPS = "groupNames";
-  public static final String FIELD_AUTHORIZATION_USERS = "users";
-  public static final String FIELD_AUTHORIZATION_UPDATED_AT = "updatedAt";
-
-  private final Settings settings;
-
-  public ProjectMeasuresIndexDefinition(Settings settings) {
-    this.settings = settings;
-  }
-
-  @Override
-  public void define(IndexDefinitionContext context) {
-    NewIndex index = context.create(INDEX_PROJECT_MEASURES);
-    index.refreshHandledByIndexer();
-    index.configureShards(settings, 5);
-
-    // type "projectmeasures"
-    NewIndex.NewIndexType mapping = index.createType(TYPE_PROJECT_MEASURES);
-    mapping.setAttribute("_parent", ImmutableMap.of("type", TYPE_AUTHORIZATION));
-    mapping.setAttribute("_routing", ImmutableMap.of("required", "true"));
-    mapping.stringFieldBuilder(FIELD_KEY).disableNorms().build();
-    mapping.stringFieldBuilder(FIELD_NAME).enableSorting().build();
-    mapping.stringFieldBuilder(FIELD_QUALITY_GATE).build();
-    mapping.createDateTimeField(FIELD_ANALYSED_AT);
-    mapping.nestedFieldBuilder(FIELD_MEASURES)
-      .addStringFied(FIELD_MEASURES_KEY)
-      .addDoubleField(FIELD_MEASURES_VALUE)
-      .build();
-
-    // do not store document but only indexation of information
-    mapping.setEnableSource(false);
-
-    // type "authorization"
-    NewIndex.NewIndexType authorizationMapping = index.createType(TYPE_AUTHORIZATION);
-    authorizationMapping.setAttribute("_routing", ImmutableMap.of("required", "true"));
-    authorizationMapping.createDateTimeField(FIELD_AUTHORIZATION_UPDATED_AT);
-    authorizationMapping.stringFieldBuilder(FIELD_AUTHORIZATION_PROJECT_UUID).disableNorms().build();
-    authorizationMapping.stringFieldBuilder(FIELD_AUTHORIZATION_GROUPS).disableNorms().build();
-    authorizationMapping.stringFieldBuilder(FIELD_AUTHORIZATION_USERS).disableNorms().build();
-    authorizationMapping.setEnableSource(false);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresIndexer.java b/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresIndexer.java
deleted file mode 100644 (file)
index 15d44c7..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.server.component.es;
-
-import java.util.Date;
-import java.util.Iterator;
-import javax.annotation.Nullable;
-import org.elasticsearch.action.index.IndexRequest;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.measure.ProjectMeasuresIndexerIterator;
-import org.sonar.db.measure.ProjectMeasuresIndexerIterator.ProjectMeasures;
-import org.sonar.server.es.BaseIndexer;
-import org.sonar.server.es.BulkIndexer;
-import org.sonar.server.es.EsClient;
-
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.TYPE_AUTHORIZATION;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
-
-public class ProjectMeasuresIndexer extends BaseIndexer {
-
-  private final DbClient dbClient;
-
-  public ProjectMeasuresIndexer(System2 system2, DbClient dbClient, EsClient esClient) {
-    super(system2, esClient, 300, INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, FIELD_ANALYSED_AT);
-    this.dbClient = dbClient;
-  }
-
-  @Override
-  protected long doIndex(long lastUpdatedAt) {
-    return doIndex(createBulkIndexer(false), lastUpdatedAt, null);
-  }
-
-  public void index(String projectUuid) {
-    doIndex(createBulkIndexer(false), 0L, projectUuid);
-  }
-
-  public void deleteProject(String uuid) {
-    esClient
-      .prepareDelete(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, uuid)
-      .setRouting(uuid)
-      .setRefresh(true)
-      .get();
-    esClient
-      .prepareDelete(INDEX_PROJECT_MEASURES, TYPE_AUTHORIZATION, uuid)
-      .setRouting(uuid)
-      .setRefresh(true)
-      .get();
-  }
-
-  private long doIndex(BulkIndexer bulk, long lastUpdatedAt, @Nullable String projectUuid) {
-    try (DbSession dbSession = dbClient.openSession(false);
-      ProjectMeasuresIndexerIterator rowIt = ProjectMeasuresIndexerIterator.create(dbSession, lastUpdatedAt, projectUuid)) {
-      return doIndex(bulk, rowIt);
-    }
-  }
-
-  private static long doIndex(BulkIndexer bulk, Iterator<ProjectMeasures> docs) {
-    bulk.start();
-    long maxDate = 0L;
-    while (docs.hasNext()) {
-      ProjectMeasures doc = docs.next();
-      bulk.add(newIndexRequest(toProjectMeasuresDoc(doc)));
-
-      Long analysisDate = doc.getProject().getAnalysisDate();
-      // it's more efficient to sort programmatically than in SQL on some databases (MySQL for instance)
-      maxDate = Math.max(maxDate, analysisDate == null ? 0L : analysisDate);
-    }
-    bulk.stop();
-    return maxDate;
-  }
-
-  private BulkIndexer createBulkIndexer(boolean large) {
-    BulkIndexer bulk = new BulkIndexer(esClient, INDEX_PROJECT_MEASURES);
-    bulk.setLarge(large);
-    return bulk;
-  }
-
-  private static IndexRequest newIndexRequest(ProjectMeasuresDoc doc) {
-    String projectUuid = doc.getId();
-    return new IndexRequest(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, projectUuid)
-      .routing(projectUuid)
-      .parent(projectUuid)
-      .source(doc.getFields());
-  }
-
-  private static ProjectMeasuresDoc toProjectMeasuresDoc(ProjectMeasures projectMeasures) {
-    Long analysisDate = projectMeasures.getProject().getAnalysisDate();
-    return new ProjectMeasuresDoc()
-      .setId(projectMeasures.getProject().getUuid())
-      .setKey(projectMeasures.getProject().getKey())
-      .setName(projectMeasures.getProject().getName())
-      .setQualityGate(projectMeasures.getMeasures().getQualityGateStatus())
-      .setAnalysedAt(analysisDate == null ? null : new Date(analysisDate))
-      .setMeasuresFromMap(projectMeasures.getMeasures().getNumericMeasures());
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresQuery.java b/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectMeasuresQuery.java
deleted file mode 100644 (file)
index 64da007..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.server.component.es;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import org.sonar.api.measures.Metric;
-
-import static com.google.common.base.Preconditions.checkState;
-import static java.lang.String.format;
-import static java.util.Arrays.stream;
-import static java.util.Objects.requireNonNull;
-
-public class ProjectMeasuresQuery {
-  private List<MetricCriterion> metricCriteria = new ArrayList<>();
-  private Metric.Level qualityGateStatus;
-  private Set<String> projectUuids = null;
-
-  public ProjectMeasuresQuery addMetricCriterion(MetricCriterion metricCriterion) {
-    this.metricCriteria.add(metricCriterion);
-    return this;
-  }
-
-  public List<MetricCriterion> getMetricCriteria() {
-    return metricCriteria;
-  }
-
-  public ProjectMeasuresQuery setQualityGateStatus(Metric.Level qualityGateStatus) {
-    this.qualityGateStatus = requireNonNull(qualityGateStatus);
-    return this;
-  }
-
-  public boolean hasQualityGateStatus() {
-    return qualityGateStatus != null;
-  }
-
-  public Metric.Level getQualityGateStatus() {
-    checkState(qualityGateStatus != null);
-    return qualityGateStatus;
-  }
-
-  public ProjectMeasuresQuery setProjectUuids(Set<String> projectUuids) {
-    this.projectUuids = requireNonNull(projectUuids);
-    return this;
-  }
-
-  public boolean doesFilterOnProjectUuids() {
-    return projectUuids != null;
-  }
-
-  public Set<String> getProjectUuids() {
-    return requireNonNull(projectUuids);
-  }
-
-  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)));
-    }
-  }
-
-  public static class MetricCriterion {
-    private final String metricKey;
-    private final Operator operator;
-    private final double value;
-
-    public MetricCriterion(String metricKey, Operator operator, double value) {
-      this.metricKey = requireNonNull(metricKey);
-      this.operator = requireNonNull(operator);
-      this.value = requireNonNull(value);
-    }
-
-    public String getMetricKey() {
-      return metricKey;
-    }
-
-    public Operator getOperator() {
-      return operator;
-    }
-
-    public double getValue() {
-      return value;
-    }
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectsEsModule.java b/server/sonar-server/src/main/java/org/sonar/server/component/es/ProjectsEsModule.java
deleted file mode 100644 (file)
index 90433cf..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.server.component.es;
-
-import org.sonar.core.platform.Module;
-
-public class ProjectsEsModule extends Module {
-  @Override
-  protected void configureModule() {
-    add(
-      ProjectMeasuresIndexDefinition.class,
-      ProjectMeasuresIndex.class,
-      ProjectMeasuresIndexer.class);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/es/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/component/es/package-info.java
deleted file mode 100644 (file)
index 0119283..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.component.es;
-
-import javax.annotation.ParametersAreNonnullByDefault;
index da1b8dd6fd1819fa43fcc7df8705038366de9316..294e833b7c5cf7a9cbf9ace3e8cd2882b2a2e35b 100644 (file)
@@ -26,13 +26,13 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.measures.Metric.Level;
-import org.sonar.server.component.es.ProjectMeasuresQuery;
+import org.sonar.server.measure.index.ProjectMeasuresQuery;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Locale.ENGLISH;
 import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
-import static org.sonar.server.component.es.ProjectMeasuresQuery.MetricCriterion;
-import static org.sonar.server.component.es.ProjectMeasuresQuery.Operator;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator;
 
 class ProjectMeasuresQueryFactory {
   private static final Splitter CRITERIA_SPLITTER = Splitter.on(Pattern.compile("and", Pattern.CASE_INSENSITIVE));
index fba71e464e4f321f619a6b96f6d53f45cf51783b..a176fb3bb85f902f8aa41562777cf73280e3a7f0 100644 (file)
@@ -28,9 +28,9 @@ 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.component.es.ProjectMeasuresQuery;
+import org.sonar.server.measure.index.ProjectMeasuresQuery;
 
-import static org.sonar.server.component.es.ProjectMeasuresQuery.MetricCriterion;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
 
 public class ProjectMeasuresQueryValidator {
 
index edfb1d290e6597a22105517392988c983c644757..8bbc7cdda0b1d6f792ff7fe9db6c9662e96312e1 100644 (file)
@@ -42,12 +42,12 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.property.PropertyDto;
 import org.sonar.db.property.PropertyQuery;
-import org.sonar.server.component.es.ProjectMeasuresIndex;
-import org.sonar.server.component.es.ProjectMeasuresQuery;
 import org.sonar.server.es.Facets;
 import org.sonar.server.es.SearchIdResult;
 import org.sonar.server.es.SearchOptions;
 import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.measure.index.ProjectMeasuresIndex;
+import org.sonar.server.measure.index.ProjectMeasuresQuery;
 import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.Common;
 import org.sonarqube.ws.WsComponents.Component;
@@ -56,8 +56,8 @@ 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.server.component.es.ProjectMeasuresIndex.SUPPORTED_FACETS;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
+import static org.sonar.server.measure.index.ProjectMeasuresIndex.SUPPORTED_FACETS;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FILTER;
 import static org.sonarqube.ws.client.component.SearchProjectsRequest.DEFAULT_PAGE_SIZE;
index 98febe3c30ff48ac97c0b63a61b0349ae88f9a14..d25077362e88bf6b6f4826c0e13ec7af7e31d524 100644 (file)
@@ -21,7 +21,7 @@ package org.sonar.server.computation.task.projectanalysis.step;
 
 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
 import org.sonar.server.computation.task.step.ComputationStep;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 
 public class IndexProjectMeasuresStep implements ComputationStep {
 
index 8887ebe001f6991b042dfda8f6853dc66fbe9249..49b591f6e0db68e9e1c4c4363629db2c9722376f 100644 (file)
@@ -22,8 +22,8 @@ package org.sonar.server.es;
 import org.sonar.api.config.Settings;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
 import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.permission.index.PermissionIndexer;
 import org.sonar.server.test.index.TestIndexer;
 import org.sonar.server.user.index.UserIndexer;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresDoc.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresDoc.java
new file mode 100644 (file)
index 0000000..9696396
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import com.google.common.collect.ImmutableMap;
+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.core.util.stream.Collectors;
+import org.sonar.server.es.BaseDoc;
+
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
+
+public class ProjectMeasuresDoc extends BaseDoc {
+
+  public ProjectMeasuresDoc() {
+    super(new HashMap<>(4));
+  }
+
+  @Override
+  public String getId() {
+    return getField("_id");
+  }
+
+  @Override
+  public String getRouting() {
+    return getId();
+  }
+
+  @Override
+  public String getParent() {
+    return getId();
+  }
+
+  public ProjectMeasuresDoc setId(String s) {
+    setField("_id", s);
+    return this;
+  }
+
+  public String getKey() {
+    return getField(ProjectMeasuresIndexDefinition.FIELD_KEY);
+  }
+
+  public ProjectMeasuresDoc setKey(String s) {
+    setField(ProjectMeasuresIndexDefinition.FIELD_KEY, s);
+    return this;
+  }
+
+  public String getName() {
+    return getField(ProjectMeasuresIndexDefinition.FIELD_NAME);
+  }
+
+  public ProjectMeasuresDoc setName(String s) {
+    setField(ProjectMeasuresIndexDefinition.FIELD_NAME, s);
+    return this;
+  }
+
+  @CheckForNull
+  public Date getAnalysedAt() {
+    return getNullableField(ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT);
+  }
+
+  public ProjectMeasuresDoc setAnalysedAt(@Nullable Date d) {
+    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;
+  }
+
+  public ProjectMeasuresDoc setMeasuresFromMap(Map<String, Double> measures) {
+    setMeasures(
+      measures.entrySet().stream()
+        .map(entry -> ImmutableMap.<String, Object>of(
+          ProjectMeasuresIndexDefinition.FIELD_MEASURES_KEY, entry.getKey(),
+          ProjectMeasuresIndexDefinition.FIELD_MEASURES_VALUE, entry.getValue()))
+        .collect(Collectors.toList()));
+    return this;
+  }
+
+  @CheckForNull
+  public String getQualityGate() {
+    return getField(ProjectMeasuresIndexDefinition.FIELD_QUALITY_GATE);
+  }
+
+  public ProjectMeasuresDoc setQualityGate(@Nullable String s) {
+    setField(ProjectMeasuresIndexDefinition.FIELD_QUALITY_GATE, s);
+    return this;
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
new file mode 100644 (file)
index 0000000..0fc4a85
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.IntStream;
+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.aggregations.AggregationBuilder;
+import org.elasticsearch.search.aggregations.AggregationBuilders;
+import org.elasticsearch.search.aggregations.bucket.range.RangeBuilder;
+import org.elasticsearch.search.sort.SortOrder;
+import org.sonar.api.measures.Metric;
+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 org.sonar.server.es.StickyFacetBuilder;
+import org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+import org.sonar.server.user.UserSession;
+
+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.elasticsearch.index.query.QueryBuilders.termsQuery;
+import static org.elasticsearch.search.aggregations.AggregationBuilders.filters;
+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;
+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_AUTHORIZATION_GROUPS;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_AUTHORIZATION_USERS;
+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_QUALITY_GATE;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_AUTHORIZATION;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+
+public class ProjectMeasuresIndex extends BaseIndex {
+
+  public static final List<String> SUPPORTED_FACETS = ImmutableList.of(
+    NCLOC_KEY,
+    DUPLICATED_LINES_DENSITY_KEY,
+    COVERAGE_KEY,
+    SQALE_RATING_KEY,
+    RELIABILITY_RATING_KEY,
+    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 final UserSession userSession;
+
+  public ProjectMeasuresIndex(EsClient client, UserSession userSession) {
+    super(client);
+    this.userSession = userSession;
+  }
+
+  public SearchIdResult<String> search(ProjectMeasuresQuery query, SearchOptions searchOptions) {
+    SearchRequestBuilder requestBuilder = getClient()
+      .prepareSearch(INDEX_PROJECT_MEASURES)
+      .setTypes(TYPE_PROJECT_MEASURES)
+      .setFetchSource(false)
+      .setFrom(searchOptions.getOffset())
+      .setSize(searchOptions.getLimit())
+      .addSort(FIELD_NAME + "." + SORT_SUFFIX, SortOrder.ASC);
+
+    BoolQueryBuilder esFilter = boolQuery();
+    Map<String, QueryBuilder> filters = createFilters(query);
+    filters.values().forEach(esFilter::must);
+    requestBuilder.setQuery(esFilter);
+
+    addFacets(requestBuilder, searchOptions, filters);
+    return new SearchIdResult<>(requestBuilder.get(), id -> id);
+  }
+
+  private static void addFacets(SearchRequestBuilder esSearch, SearchOptions options, Map<String, QueryBuilder> filters) {
+    if (!options.getFacets().isEmpty()) {
+      if (options.getFacets().contains(NCLOC_KEY)) {
+        addRangeFacet(esSearch, NCLOC_KEY, ImmutableList.of(1_000d, 10_000d, 100_000d, 500_000d), filters);
+      }
+      if (options.getFacets().contains(DUPLICATED_LINES_DENSITY_KEY)) {
+        addRangeFacet(esSearch, DUPLICATED_LINES_DENSITY_KEY, ImmutableList.of(3d, 5d, 10d, 20d), filters);
+      }
+      if (options.getFacets().contains(COVERAGE_KEY)) {
+        addRangeFacet(esSearch, COVERAGE_KEY, ImmutableList.of(30d, 50d, 70d, 80d), filters);
+      }
+      if (options.getFacets().contains(SQALE_RATING_KEY)) {
+        addRatingFacet(esSearch, SQALE_RATING_KEY, filters);
+      }
+      if (options.getFacets().contains(RELIABILITY_RATING_KEY)) {
+        addRatingFacet(esSearch, RELIABILITY_RATING_KEY, filters);
+      }
+      if (options.getFacets().contains(SECURITY_RATING_KEY)) {
+        addRatingFacet(esSearch, SECURITY_RATING_KEY, filters);
+      }
+      if (options.getFacets().contains(ALERT_STATUS_KEY)) {
+        esSearch.addAggregation(createStickyFacet(ALERT_STATUS_KEY, filters, createQualityGateFacet()));
+      }
+    }
+  }
+
+  private static void addRangeFacet(SearchRequestBuilder esSearch, String metricKey, List<Double> thresholds, Map<String, QueryBuilder> filters) {
+    esSearch.addAggregation(createStickyFacet(metricKey, filters, createRangeFacet(metricKey, thresholds)));
+  }
+
+  private static void addRatingFacet(SearchRequestBuilder esSearch, String metricKey, Map<String, QueryBuilder> filters) {
+    esSearch.addAggregation(createStickyFacet(metricKey, filters, createRatingFacet(metricKey)));
+  }
+
+  private static AggregationBuilder createStickyFacet(String metricKey, Map<String, QueryBuilder> filters, AggregationBuilder aggregationBuilder) {
+    StickyFacetBuilder facetBuilder = new StickyFacetBuilder(matchAllQuery(), filters);
+    BoolQueryBuilder facetFilter = facetBuilder.getStickyFacetFilter(metricKey);
+    return AggregationBuilders
+      .global(metricKey)
+      .subAggregation(AggregationBuilders.filter("facet_filter_" + metricKey)
+        .filter(facetFilter)
+        .subAggregation(aggregationBuilder));
+  }
+
+  private static AggregationBuilder createRangeFacet(String metricKey, List<Double> thresholds) {
+    RangeBuilder rangeAgg = AggregationBuilders.range(metricKey)
+      .field(FIELD_VALUE);
+    final int lastIndex = thresholds.size() - 1;
+    IntStream.range(0, thresholds.size())
+      .forEach(i -> {
+        if (i == 0) {
+          rangeAgg.addUnboundedTo(thresholds.get(0));
+          rangeAgg.addRange(thresholds.get(0), thresholds.get(1));
+        } else if (i == lastIndex) {
+          rangeAgg.addUnboundedFrom(thresholds.get(lastIndex));
+        } else {
+          rangeAgg.addRange(thresholds.get(i), thresholds.get(i + 1));
+        }
+      });
+
+    return AggregationBuilders.nested("nested_" + metricKey)
+      .path(FIELD_MEASURES)
+      .subAggregation(
+        AggregationBuilders.filter("filter_" + metricKey)
+          .filter(termsQuery(FIELD_KEY, metricKey))
+          .subAggregation(rangeAgg));
+  }
+
+  private static AggregationBuilder createRatingFacet(String metricKey) {
+    return AggregationBuilders.nested("nested_" + metricKey)
+      .path(FIELD_MEASURES)
+      .subAggregation(
+        AggregationBuilders.filter("filter_" + metricKey)
+          .filter(termsQuery(FIELD_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))));
+  }
+
+  private static AggregationBuilder createQualityGateFacet() {
+    return AggregationBuilders.filters(ALERT_STATUS_KEY)
+      .filter(Metric.Level.ERROR.name(), termQuery(FIELD_QUALITY_GATE, Metric.Level.ERROR.name()))
+      .filter(Metric.Level.WARN.name(), termQuery(FIELD_QUALITY_GATE, Metric.Level.WARN.name()))
+      .filter(Metric.Level.OK.name(), termQuery(FIELD_QUALITY_GATE, Metric.Level.OK.name()));
+  }
+
+  private Map<String, QueryBuilder> createFilters(ProjectMeasuresQuery query) {
+    Map<String, QueryBuilder> filters = new HashMap<>();
+    filters.put("__authorization", createAuthorizationFilter());
+    Multimap<String, MetricCriterion> metricCriterionMultimap = ArrayListMultimap.create();
+    query.getMetricCriteria().forEach(metricCriterion -> metricCriterionMultimap.put(metricCriterion.getMetricKey(), metricCriterion));
+    metricCriterionMultimap.asMap().entrySet().forEach(entry -> {
+      BoolQueryBuilder metricFilters = boolQuery();
+      entry.getValue()
+        .stream()
+        .map(criterion -> nestedQuery(FIELD_MEASURES, boolQuery()
+          .filter(termQuery(FIELD_KEY, criterion.getMetricKey()))
+          .filter(toValueQuery(criterion))))
+        .forEach(metricFilters::must);
+      filters.put(entry.getKey(), metricFilters);
+
+    });
+    if (query.hasQualityGateStatus()) {
+      filters.put(ALERT_STATUS_KEY, termQuery(FIELD_QUALITY_GATE, query.getQualityGateStatus().name()));
+    }
+    if (query.doesFilterOnProjectUuids()) {
+      filters.put("ids", termsQuery("_id", query.getProjectUuids()));
+    }
+    return filters;
+  }
+
+  private static QueryBuilder toValueQuery(MetricCriterion criterion) {
+    String fieldName = FIELD_VALUE;
+
+    switch (criterion.getOperator()) {
+      case GT:
+        return rangeQuery(fieldName).gt(criterion.getValue());
+      case GTE:
+        return rangeQuery(fieldName).gte(criterion.getValue());
+      case LT:
+        return rangeQuery(fieldName).lt(criterion.getValue());
+      case LTE:
+        return rangeQuery(fieldName).lte(criterion.getValue());
+      case EQ:
+        return termQuery(fieldName, criterion.getValue());
+      default:
+        throw new IllegalStateException("Metric criteria non supported: " + criterion.getOperator().name());
+    }
+  }
+
+  private QueryBuilder createAuthorizationFilter() {
+    Integer userLogin = userSession.getUserId();
+    Set<String> userGroupNames = userSession.getUserGroups();
+    BoolQueryBuilder groupsAndUser = boolQuery();
+    if (userLogin != null) {
+      groupsAndUser.should(termQuery(FIELD_AUTHORIZATION_USERS, userLogin.longValue()));
+    }
+    for (String group : userGroupNames) {
+      groupsAndUser.should(termQuery(FIELD_AUTHORIZATION_GROUPS, group));
+    }
+    return QueryBuilders.hasParentQuery(TYPE_AUTHORIZATION,
+      QueryBuilders.boolQuery().must(matchAllQuery()).filter(groupsAndUser));
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexDefinition.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexDefinition.java
new file mode 100644 (file)
index 0000000..d4fbe2d
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import com.google.common.collect.ImmutableMap;
+import org.sonar.api.config.Settings;
+import org.sonar.server.es.IndexDefinition;
+import org.sonar.server.es.NewIndex;
+
+public class ProjectMeasuresIndexDefinition implements IndexDefinition {
+
+  public static final String INDEX_PROJECT_MEASURES = "projectmeasures";
+
+  public static final String TYPE_PROJECT_MEASURES = "projectmeasures";
+  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_QUALITY_GATE = "qualityGate";
+  public static final String FIELD_MEASURES = "measures";
+  public static final String FIELD_MEASURES_KEY = "key";
+  public static final String FIELD_MEASURES_VALUE = "value";
+
+  public static final String TYPE_AUTHORIZATION = "authorization";
+  public static final String FIELD_AUTHORIZATION_PROJECT_UUID = "project";
+  public static final String FIELD_AUTHORIZATION_GROUPS = "groupNames";
+  public static final String FIELD_AUTHORIZATION_USERS = "users";
+  public static final String FIELD_AUTHORIZATION_UPDATED_AT = "updatedAt";
+
+  private final Settings settings;
+
+  public ProjectMeasuresIndexDefinition(Settings settings) {
+    this.settings = settings;
+  }
+
+  @Override
+  public void define(IndexDefinitionContext context) {
+    NewIndex index = context.create(INDEX_PROJECT_MEASURES);
+    index.refreshHandledByIndexer();
+    index.configureShards(settings, 5);
+
+    // type "projectmeasures"
+    NewIndex.NewIndexType mapping = index.createType(TYPE_PROJECT_MEASURES);
+    mapping.setAttribute("_parent", ImmutableMap.of("type", TYPE_AUTHORIZATION));
+    mapping.setAttribute("_routing", ImmutableMap.of("required", "true"));
+    mapping.stringFieldBuilder(FIELD_KEY).disableNorms().build();
+    mapping.stringFieldBuilder(FIELD_NAME).enableSorting().build();
+    mapping.stringFieldBuilder(FIELD_QUALITY_GATE).build();
+    mapping.createDateTimeField(FIELD_ANALYSED_AT);
+    mapping.nestedFieldBuilder(FIELD_MEASURES)
+      .addStringFied(FIELD_MEASURES_KEY)
+      .addDoubleField(FIELD_MEASURES_VALUE)
+      .build();
+
+    // do not store document but only indexation of information
+    mapping.setEnableSource(false);
+
+    // type "authorization"
+    NewIndex.NewIndexType authorizationMapping = index.createType(TYPE_AUTHORIZATION);
+    authorizationMapping.setAttribute("_routing", ImmutableMap.of("required", "true"));
+    authorizationMapping.createDateTimeField(FIELD_AUTHORIZATION_UPDATED_AT);
+    authorizationMapping.stringFieldBuilder(FIELD_AUTHORIZATION_PROJECT_UUID).disableNorms().build();
+    authorizationMapping.stringFieldBuilder(FIELD_AUTHORIZATION_GROUPS).disableNorms().build();
+    authorizationMapping.stringFieldBuilder(FIELD_AUTHORIZATION_USERS).disableNorms().build();
+    authorizationMapping.setEnableSource(false);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java
new file mode 100644 (file)
index 0000000..b9ff5d5
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.measure.index;
+
+import java.util.Date;
+import java.util.Iterator;
+import javax.annotation.Nullable;
+import org.elasticsearch.action.index.IndexRequest;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.measure.ProjectMeasuresIndexerIterator;
+import org.sonar.db.measure.ProjectMeasuresIndexerIterator.ProjectMeasures;
+import org.sonar.server.es.BaseIndexer;
+import org.sonar.server.es.BulkIndexer;
+import org.sonar.server.es.EsClient;
+
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_AUTHORIZATION;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+
+public class ProjectMeasuresIndexer extends BaseIndexer {
+
+  private final DbClient dbClient;
+
+  public ProjectMeasuresIndexer(System2 system2, DbClient dbClient, EsClient esClient) {
+    super(system2, esClient, 300, INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, FIELD_ANALYSED_AT);
+    this.dbClient = dbClient;
+  }
+
+  @Override
+  protected long doIndex(long lastUpdatedAt) {
+    return doIndex(createBulkIndexer(false), lastUpdatedAt, null);
+  }
+
+  public void index(String projectUuid) {
+    doIndex(createBulkIndexer(false), 0L, projectUuid);
+  }
+
+  public void deleteProject(String uuid) {
+    esClient
+      .prepareDelete(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, uuid)
+      .setRouting(uuid)
+      .setRefresh(true)
+      .get();
+    esClient
+      .prepareDelete(INDEX_PROJECT_MEASURES, TYPE_AUTHORIZATION, uuid)
+      .setRouting(uuid)
+      .setRefresh(true)
+      .get();
+  }
+
+  private long doIndex(BulkIndexer bulk, long lastUpdatedAt, @Nullable String projectUuid) {
+    try (DbSession dbSession = dbClient.openSession(false);
+      ProjectMeasuresIndexerIterator rowIt = ProjectMeasuresIndexerIterator.create(dbSession, lastUpdatedAt, projectUuid)) {
+      return doIndex(bulk, rowIt);
+    }
+  }
+
+  private static long doIndex(BulkIndexer bulk, Iterator<ProjectMeasures> docs) {
+    bulk.start();
+    long maxDate = 0L;
+    while (docs.hasNext()) {
+      ProjectMeasures doc = docs.next();
+      bulk.add(newIndexRequest(toProjectMeasuresDoc(doc)));
+
+      Long analysisDate = doc.getProject().getAnalysisDate();
+      // it's more efficient to sort programmatically than in SQL on some databases (MySQL for instance)
+      maxDate = Math.max(maxDate, analysisDate == null ? 0L : analysisDate);
+    }
+    bulk.stop();
+    return maxDate;
+  }
+
+  private BulkIndexer createBulkIndexer(boolean large) {
+    BulkIndexer bulk = new BulkIndexer(esClient, INDEX_PROJECT_MEASURES);
+    bulk.setLarge(large);
+    return bulk;
+  }
+
+  private static IndexRequest newIndexRequest(ProjectMeasuresDoc doc) {
+    String projectUuid = doc.getId();
+    return new IndexRequest(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, projectUuid)
+      .routing(projectUuid)
+      .parent(projectUuid)
+      .source(doc.getFields());
+  }
+
+  private static ProjectMeasuresDoc toProjectMeasuresDoc(ProjectMeasures projectMeasures) {
+    Long analysisDate = projectMeasures.getProject().getAnalysisDate();
+    return new ProjectMeasuresDoc()
+      .setId(projectMeasures.getProject().getUuid())
+      .setKey(projectMeasures.getProject().getKey())
+      .setName(projectMeasures.getProject().getName())
+      .setQualityGate(projectMeasures.getMeasures().getQualityGateStatus())
+      .setAnalysedAt(analysisDate == null ? null : new Date(analysisDate))
+      .setMeasuresFromMap(projectMeasures.getMeasures().getNumericMeasures());
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java
new file mode 100644 (file)
index 0000000..050cff2
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.measure.index;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.sonar.api.measures.Metric;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+import static java.util.Arrays.stream;
+import static java.util.Objects.requireNonNull;
+
+public class ProjectMeasuresQuery {
+  private List<MetricCriterion> metricCriteria = new ArrayList<>();
+  private Metric.Level qualityGateStatus;
+  private Set<String> projectUuids = null;
+
+  public ProjectMeasuresQuery addMetricCriterion(MetricCriterion metricCriterion) {
+    this.metricCriteria.add(metricCriterion);
+    return this;
+  }
+
+  public List<MetricCriterion> getMetricCriteria() {
+    return metricCriteria;
+  }
+
+  public ProjectMeasuresQuery setQualityGateStatus(Metric.Level qualityGateStatus) {
+    this.qualityGateStatus = requireNonNull(qualityGateStatus);
+    return this;
+  }
+
+  public boolean hasQualityGateStatus() {
+    return qualityGateStatus != null;
+  }
+
+  public Metric.Level getQualityGateStatus() {
+    checkState(qualityGateStatus != null);
+    return qualityGateStatus;
+  }
+
+  public ProjectMeasuresQuery setProjectUuids(Set<String> projectUuids) {
+    this.projectUuids = requireNonNull(projectUuids);
+    return this;
+  }
+
+  public boolean doesFilterOnProjectUuids() {
+    return projectUuids != null;
+  }
+
+  public Set<String> getProjectUuids() {
+    return requireNonNull(projectUuids);
+  }
+
+  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)));
+    }
+  }
+
+  public static class MetricCriterion {
+    private final String metricKey;
+    private final Operator operator;
+    private final double value;
+
+    public MetricCriterion(String metricKey, Operator operator, double value) {
+      this.metricKey = requireNonNull(metricKey);
+      this.operator = requireNonNull(operator);
+      this.value = requireNonNull(value);
+    }
+
+    public String getMetricKey() {
+      return metricKey;
+    }
+
+    public Operator getOperator() {
+      return operator;
+    }
+
+    public double getValue() {
+      return value;
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectsEsModule.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectsEsModule.java
new file mode 100644 (file)
index 0000000..2a060f1
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.measure.index;
+
+import org.sonar.core.platform.Module;
+
+public class ProjectsEsModule extends Module {
+  @Override
+  protected void configureModule() {
+    add(
+      ProjectMeasuresIndexDefinition.class,
+      ProjectMeasuresIndex.class,
+      ProjectMeasuresIndexer.class);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/package-info.java
new file mode 100644 (file)
index 0000000..4de8f4c
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.measure.index;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index b42a43f8010da031f6a096de02f77886d8f640ad..39a70eb072d6e18ab8826ab8098017c2c235bc73 100644 (file)
@@ -38,11 +38,11 @@ import org.elasticsearch.action.search.SearchResponse;
 import org.picocontainer.Startable;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
 import org.sonar.server.es.BulkIndexer;
 import org.sonar.server.es.EsClient;
 import org.sonar.server.es.EsUtils;
 import org.sonar.server.issue.index.IssueIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Collections.singletonList;
index 0f3f7388a4bcc5d14fc209250427154a8253e9fc..c1c68776e4a2e3c82a91e8f0d3a7dd3089a4d6c5 100644 (file)
@@ -33,10 +33,10 @@ import org.sonar.api.utils.log.Loggers;
 import org.sonar.db.DbSession;
 import org.sonar.db.MyBatis;
 import org.sonar.db.version.SqTables;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
 import org.sonar.server.es.BulkIndexer;
 import org.sonar.server.es.EsClient;
 import org.sonar.server.issue.index.IssueIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
 import org.sonar.server.property.InternalProperties;
 import org.sonar.server.view.index.ViewIndexDefinition;
 
index 334ac275f43adec53bb2db6d50652064060b9fa8..85de4ddfa76239f0ad0b36612982e908f31108c6 100644 (file)
@@ -40,7 +40,6 @@ import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.component.ComponentService;
 import org.sonar.server.component.DefaultComponentFinder;
 import org.sonar.server.component.DefaultRubyComponentService;
-import org.sonar.server.component.es.ProjectsEsModule;
 import org.sonar.server.component.ws.ComponentsWsModule;
 import org.sonar.server.debt.DebtModelBackup;
 import org.sonar.server.debt.DebtModelPluginRepository;
@@ -76,6 +75,7 @@ import org.sonar.server.issue.ws.IssueWsModule;
 import org.sonar.server.language.ws.LanguageWs;
 import org.sonar.server.license.ws.LicensesWsModule;
 import org.sonar.server.measure.custom.ws.CustomMeasuresWsModule;
+import org.sonar.server.measure.index.ProjectsEsModule;
 import org.sonar.server.measure.template.MyFavouritesFilter;
 import org.sonar.server.measure.template.ProjectFilter;
 import org.sonar.server.measure.ws.MeasuresWsModule;
index 899a0ff46e71bcbc1a75c98c19995e1e1b241b72..cff9a8ee98bc210e7ad76c1d00d74f11c8b67df8 100644 (file)
@@ -39,13 +39,13 @@ import org.sonar.db.issue.IssueDto;
 import org.sonar.db.issue.IssueTesting;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleTesting;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.issue.IssueDocTesting;
 import org.sonar.server.issue.index.IssueIndexDefinition;
 import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.permission.index.PermissionIndexer;
 import org.sonar.server.test.index.TestDoc;
 import org.sonar.server.test.index.TestIndexDefinition;
index 21a886bfd85a7bd5a90b49c19b9e1c0540e7c030..ed086db14933dd0798c31bffa1f197ec54cca778 100644 (file)
@@ -38,12 +38,12 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
 import org.sonar.db.component.ResourceIndexDao;
 import org.sonar.db.organization.OrganizationDto;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.i18n.I18nRule;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.tester.UserSessionRule;
 
 import static com.google.common.collect.Lists.newArrayList;
@@ -60,8 +60,8 @@ import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.component.ComponentTesting.newModuleDto;
 import static org.sonar.db.component.ComponentTesting.newProjectDto;
 import static org.sonar.server.component.NewComponent.newComponentBuilder;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
 
 public class ComponentServiceTest {
 
index 2ff33d50a172d28f88f94d2c552d6fa73035427f..8896afc6c44cb4dff745e6560e30cf8d938fb2e3 100644 (file)
@@ -35,12 +35,12 @@ import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.i18n.I18nRule;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.tester.UserSessionRule;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -51,8 +51,8 @@ import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.component.ComponentTesting.newModuleDto;
 import static org.sonar.db.component.ComponentTesting.newProjectDto;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
 
 public class ComponentServiceUpdateKeyTest {
 
index 9529f0367924ff435d9f08c3ca42554076fd3824..99891d2e9df9476e83a4fb591efb8b0ec77ae103 100644 (file)
@@ -34,14 +34,14 @@ import org.sonar.db.component.ComponentDbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ResourceDao;
 import org.sonar.db.component.ResourceDto;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.favorite.FavoriteUpdater;
 import org.sonar.server.i18n.I18nRule;
 import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.organization.TestDefaultOrganizationProvider;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.permission.PermissionTemplateService;
 import org.sonar.server.tester.UserSessionRule;
 
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresIndexTest.java
deleted file mode 100644 (file)
index 341a8d4..0000000
+++ /dev/null
@@ -1,893 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.es;
-
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.IntStream;
-import javax.annotation.Nullable;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.config.MapSettings;
-import org.sonar.server.component.es.ProjectMeasuresQuery.MetricCriterion;
-import org.sonar.server.component.es.ProjectMeasuresQuery.Operator;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.Facets;
-import org.sonar.server.es.SearchIdResult;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-import org.sonar.server.tester.UserSessionRule;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static com.google.common.collect.Sets.newHashSet;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-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.Metric.Level.ERROR;
-import static org.sonar.api.measures.Metric.Level.OK;
-import static org.sonar.api.measures.Metric.Level.WARN;
-import static org.sonar.api.security.DefaultGroups.ANYONE;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
-
-public class ProjectMeasuresIndexTest {
-
-  private static final String MAINTAINABILITY_RATING = "sqale_rating";
-  private static final String RELIABILITY_RATING = "reliability_rating";
-  private static final String SECURITY_RATING = "security_rating";
-  private static final String COVERAGE = "coverage";
-  private static final String DUPLICATION = "duplicated_lines_density";
-  private static final String NCLOC = "ncloc";
-
-  @Rule
-  public EsTester es = new EsTester(new ProjectMeasuresIndexDefinition(new MapSettings()));
-
-  @Rule
-  public UserSessionRule userSession = UserSessionRule.standalone();
-
-  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es);
-
-  private ProjectMeasuresIndex underTest = new ProjectMeasuresIndex(es.client(), userSession);
-
-  @Test
-  public void empty_search() {
-    List<String> result = underTest.search(new ProjectMeasuresQuery(), 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 ProjectMeasuresQuery(), new SearchOptions()).getIds();
-
-    assertThat(result).containsExactly("P2", "P3", "P1");
-  }
-
-  @Test
-  public void search_paginate_results() {
-    IntStream.rangeClosed(1, 9)
-      .forEach(i -> addDocs(newDoc("P" + i, "K" + i, "P" + i)));
-
-    SearchIdResult<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().setPage(2, 3));
-
-    assertThat(result.getIds()).containsExactly("P4", "P5", "P6");
-    assertThat(result.getTotal()).isEqualTo(9);
-  }
-
-  @Test
-  public void filter_with_lower_than() {
-    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))));
-
-    ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery()
-      .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LT, 80d));
-    List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
-
-    assertThat(result).containsExactly("P1");
-  }
-
-  @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))));
-
-    ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery()
-      .addMetricCriterion(new MetricCriterion(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, 30_000d))),
-      newDoc("P2", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 30_001d))),
-      newDoc("P3", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 30_001d))));
-
-    assertThat(underTest.search(new ProjectMeasuresQuery().addMetricCriterion(new MetricCriterion(NCLOC, Operator.GT, 30_000d)),
-      new SearchOptions()).getIds()).containsExactly("P2", "P3");
-    assertThat(underTest.search(new ProjectMeasuresQuery().addMetricCriterion(new MetricCriterion(NCLOC, Operator.GT, 100_000d)),
-      new SearchOptions()).getIds()).isEmpty();
-  }
-
-  @Test
-  public void filter_with_greater_than_or_equals() {
-    addDocs(
-      newDoc("P1", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 30_000d))),
-      newDoc("P2", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 30_001d))),
-      newDoc("P3", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 30_001d))));
-
-    assertThat(underTest.search(new ProjectMeasuresQuery().addMetricCriterion(new MetricCriterion(NCLOC, Operator.GTE, 30_001d)),
-      new SearchOptions()).getIds()).containsExactly("P2", "P3");
-    assertThat(underTest.search(new ProjectMeasuresQuery().addMetricCriterion(new MetricCriterion(NCLOC, Operator.GTE, 100_000d)),
-      new SearchOptions()).getIds()).isEmpty();
-  }
-
-  @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))));
-
-    ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery()
-      .addMetricCriterion(new MetricCriterion(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))));
-
-    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));
-    List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
-
-    assertThat(result).containsExactly("P2");
-  }
-
-  @Test
-  public void filter_on_quality_gate_status() {
-    addDocs(
-      newDoc("P1", "K1", "N1").setQualityGate("OK"),
-      newDoc("P2", "K2", "N2").setQualityGate("OK"),
-      newDoc("P3", "K3", "N3").setQualityGate("WARN"));
-    ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery().setQualityGateStatus(OK);
-
-    List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
-
-    assertThat(result).containsExactly("P1", "P2");
-  }
-
-  @Test
-  public void filter_on_ids() {
-    addDocs(
-      newDoc("P1", "K1", "N1"),
-      newDoc("P2", "K2", "N2"),
-      newDoc("P3", "K3", "N3"));
-    ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery().setProjectUuids(newHashSet("P1", "P3"));
-
-    List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
-
-    assertThat(result).containsExactly("P1", "P3");
-  }
-
-  @Test
-  public void return_only_projects_authorized_for_user() throws Exception {
-    userSession.login("john").setUserId(10);
-    addDocs(10L, null, newDoc("P1", "K1", "Windows"));
-    addDocs(10L, "dev", newDoc("P2", "K2", "apachee"));
-    addDocs(33L, null, newDoc("P10", "K10", "N10"));
-
-    List<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getIds();
-
-    assertThat(result).containsOnly("P1", "P2");
-  }
-
-  @Test
-  public void return_only_projects_authorized_for_user_groups() throws Exception {
-    userSession.setUserGroups("dev");
-    addDocs(10L, "dev", newDoc("P1", "K1", "apachee"));
-    addDocs(null, ANYONE, newDoc("P2", "K2", "N2"));
-    addDocs(null, "admin", newDoc("P10", "K10", "N10"));
-
-    List<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getIds();
-
-    assertThat(result).containsOnly("P1", "P2");
-  }
-
-  @Test
-  public void return_only_projects_authorized_for_user_and_groups() throws Exception {
-    userSession.login("john").setUserId(10).setUserGroups("dev");
-    addDocs(10L, null, newDoc("P1", "K1", "Windows"));
-    addDocs(null, "dev", newDoc("P2", "K2", "Apache"));
-    addDocs(10L, "dev", newDoc("P3", "K3", "apachee"));
-    // Current user is not able to see following projects
-    addDocs(null, "another group", newDoc("P5", "K5", "N5"));
-    addDocs(33L, null, newDoc("P6", "K6", "N6"));
-    addDocs((Long) null, null, newDoc("P7", "K7", "N7"));
-
-    List<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getIds();
-
-    assertThat(result).containsOnly("P1", "P2", "P3");
-  }
-
-  @Test
-  public void anyone_user_can_only_access_projects_authorized_for_anyone() throws Exception {
-    userSession.anonymous();
-    addDocs(null, ANYONE, newDoc("P1", "K1", "N1"));
-    addDocs(10L, null, newDoc("P2", "K2", "Windows"));
-    addDocs(null, "admin", newDoc("P3", "K3", "N3"));
-
-    List<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getIds();
-
-    assertThat(result).containsOnly("P1");
-  }
-
-  @Test
-  public void does_not_return_facet_when_no_facets_in_options() throws Exception {
-    addDocs(
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 10d), newMeasure(COVERAGE_KEY, 30d), newMeasure(MAINTAINABILITY_RATING, 3d)))
-        .setQualityGate(OK.name()));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getFacets();
-
-    assertThat(facets.getAll()).isEmpty();
-  }
-
-  @Test
-  public void facet_ncloc() {
-    addDocs(
-      // 3 docs with ncloc<1K
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 0d))),
-      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 0d))),
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 999d))),
-      // 2 docs with ncloc>=1K and ncloc<10K
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 1_000d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 9_999d))),
-      // 4 docs with ncloc>=10K and ncloc<100K
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 10_000d))),
-      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 10_000d))),
-      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 11_000d))),
-      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 99_000d))),
-      // 2 docs with ncloc>=100K and ncloc<500K
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 100_000d))),
-      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 499_000d))),
-      // 5 docs with ncloc>= 500K
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 500_000d))),
-      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 100_000_000d))),
-      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 500_000d))),
-      newDoc("P54", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 1_000_000d))),
-      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(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));
-  }
-
-  @Test
-  public void facet_ncloc_is_sticky() {
-    addDocs(
-      // 1 docs with ncloc<1K
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 999d), newMeasure(COVERAGE, 0d), newMeasure(DUPLICATION, 0d))),
-      // 2 docs with ncloc>=1K and ncloc<10K
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 1_000d), newMeasure(COVERAGE, 10d), newMeasure(DUPLICATION, 0d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 9_999d), newMeasure(COVERAGE, 20d), newMeasure(DUPLICATION, 0d))),
-      // 3 docs with ncloc>=10K and ncloc<100K
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 10_000d), newMeasure(COVERAGE, 31d), newMeasure(DUPLICATION, 0d))),
-      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 11_000d), newMeasure(COVERAGE, 40d), newMeasure(DUPLICATION, 0d))),
-      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 99_000d), newMeasure(COVERAGE, 50d), newMeasure(DUPLICATION, 0d))),
-      // 2 docs with ncloc>=100K and ncloc<500K
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 100_000d), newMeasure(COVERAGE, 71d), newMeasure(DUPLICATION, 0d))),
-      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 499_000d), newMeasure(COVERAGE, 80d), newMeasure(DUPLICATION, 0d))),
-      // 1 docs with ncloc>= 500K
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 501_000d), newMeasure(COVERAGE, 81d), newMeasure(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();
-
-    // 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));
-    // 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));
-  }
-
-  @Test
-  public void facet_ncloc_contains_only_projects_authorized_for_user() throws Exception {
-    userSession.login("john").setUserId(10);
-
-    // User can see these projects
-    addDocs(10L, null,
-      // docs with ncloc<1K
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 0d))),
-      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 100d))),
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 999d))),
-      // docs with ncloc>=1K and ncloc<10K
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 1_000d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 9_999d))));
-
-    // User cannot see these projects
-    addDocs(33L, null,
-      // doc with ncloc>=10K and ncloc<100K
-      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 11_000d))),
-      // doc with ncloc>=100K and ncloc<500K
-      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 499_000d))),
-      // doc with ncloc>= 500K
-      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 501_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", 0L),
-      entry("100000.0-500000.0", 0L),
-      entry("500000.0-*", 0L));
-  }
-
-  @Test
-  public void facet_coverage() {
-    addDocs(
-      // 3 docs with coverage<30%
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 0d))),
-      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 0d))),
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 29d))),
-      // 2 docs with coverage>=30% and coverage<50%
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 30d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 49d))),
-      // 4 docs with coverage>=50% and coverage<70%
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 50d))),
-      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 60d))),
-      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 60d))),
-      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 69d))),
-      // 2 docs with coverage>=70% and coverage<80%
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 70d))),
-      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 79d))),
-      // 5 docs with coverage>= 80%
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 80d))),
-      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 80d))),
-      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 90d))),
-      newDoc("P54", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 90.5d))),
-      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(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));
-  }
-
-  @Test
-  public void facet_coverage_is_sticky() {
-    addDocs(
-      // docs with coverage<30%
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 999d), newMeasure(COVERAGE, 0d), newMeasure(DUPLICATION, 0d))),
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 1_000d), newMeasure(COVERAGE, 10d), newMeasure(DUPLICATION, 0d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 9_999d), newMeasure(COVERAGE, 20d), newMeasure(DUPLICATION, 0d))),
-      // docs with coverage>=30% and coverage<50%
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 10_000d), newMeasure(COVERAGE, 31d), newMeasure(DUPLICATION, 0d))),
-      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 11_000d), newMeasure(COVERAGE, 40d), newMeasure(DUPLICATION, 0d))),
-      // docs with coverage>=50% and coverage<70%
-      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 99_000d), newMeasure(COVERAGE, 50d), newMeasure(DUPLICATION, 0d))),
-      // docs with coverage>=70% and coverage<80%
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 100_000d), newMeasure(COVERAGE, 71d), newMeasure(DUPLICATION, 0d))),
-      // docs with coverage>= 80%
-      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 499_000d), newMeasure(COVERAGE, 80d), newMeasure(DUPLICATION, 15d))),
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 501_000d), newMeasure(COVERAGE, 810d), newMeasure(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();
-
-    // 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));
-    // 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));
-  }
-
-  @Test
-  public void facet_coverage_contains_only_projects_authorized_for_user() throws Exception {
-    userSession.login("john").setUserId(10);
-
-    // User can see these projects
-    addDocs(10L, null,
-      // docs with coverage<30%
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 0d))),
-      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 0d))),
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 29d))),
-      // docs with coverage>=30% and coverage<50%
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 30d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 49d))));
-
-    // User cannot see these projects
-    addDocs(33L, null,
-      // docs with coverage>=50% and coverage<70%
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 50d))),
-      // docs with coverage>=70% and coverage<80%
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 70d))),
-      // docs with coverage>= 80%
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 80d))));
-
-    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));
-  }
-
-  @Test
-  public void facet_duplicated_lines_density() {
-    addDocs(
-      // 3 docs with duplication<3%
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 0d))),
-      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 0d))),
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 2.9d))),
-      // 2 docs with duplication>=3% and duplication<5%
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(DUPLICATION, 3d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(DUPLICATION, 4.9d))),
-      // 4 docs with duplication>=5% and duplication<10%
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 5d))),
-      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 6d))),
-      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 6d))),
-      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 9.9d))),
-      // 2 docs with duplication>=10% and duplication<20%
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 10d))),
-      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 19.9d))),
-      // 5 docs with duplication>= 20%
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 20d))),
-      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 20d))),
-      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 50d))),
-      newDoc("P54", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 80d))),
-      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(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));
-  }
-
-  @Test
-  public void facet_duplicated_lines_density_is_sticky() {
-    addDocs(
-      // docs with duplication<3%
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 0d), newMeasure(NCLOC, 999d), newMeasure(COVERAGE, 0d))),
-      // docs with duplication>=3% and duplication<5%
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(DUPLICATION, 3d), newMeasure(NCLOC, 5000d), newMeasure(COVERAGE, 0d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(DUPLICATION, 4.9d), newMeasure(NCLOC, 6000d), newMeasure(COVERAGE, 0d))),
-      // docs with duplication>=5% and duplication<10%
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 5d), newMeasure(NCLOC, 11000d), newMeasure(COVERAGE, 0d))),
-      // docs with duplication>=10% and duplication<20%
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 10d), newMeasure(NCLOC, 120000d), newMeasure(COVERAGE, 10d))),
-      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 19.9d), newMeasure(NCLOC, 130000d), newMeasure(COVERAGE, 20d))),
-      // docs with duplication>= 20%
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 20d), newMeasure(NCLOC, 1000000d), newMeasure(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();
-
-    // 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));
-    // 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));
-  }
-
-  @Test
-  public void facet_duplicated_lines_density_contains_only_projects_authorized_for_user() throws Exception {
-    userSession.login("john").setUserId(10);
-
-    // User can see these projects
-    addDocs(10L, null,
-      // docs with duplication<3%
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 0d))),
-      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 0d))),
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 2.9d))),
-      // docs with duplication>=3% and duplication<5%
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(DUPLICATION, 3d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(DUPLICATION, 4.9d))));
-
-    // User cannot see these projects
-    addDocs(33L, null,
-      // docs with duplication>=5% and duplication<10%
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 5d))),
-      // docs with duplication>=10% and duplication<20%
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 10d))),
-      // docs with duplication>= 20%
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 20d))));
-
-    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));
-  }
-
-  @Test
-  public void facet_maintainability_rating() {
-    addDocs(
-      // 3 docs with rating A
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d))),
-      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d))),
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d))),
-      // 2 docs with rating B
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 2d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 2d))),
-      // 4 docs with rating C
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d))),
-      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d))),
-      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d))),
-      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d))),
-      // 2 docs with rating D
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 4d))),
-      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 4d))),
-      // 5 docs with rating E
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d))),
-      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d))),
-      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d))),
-      newDoc("P54", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d))),
-      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(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));
-  }
-
-  @Test
-  public void facet_maintainability_rating_is_sticky() {
-    addDocs(
-      // docs with rating A
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d), newMeasure(NCLOC, 100d), newMeasure(COVERAGE, 0d))),
-      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d), newMeasure(NCLOC, 200d), newMeasure(COVERAGE, 0d))),
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d), newMeasure(NCLOC, 999d), newMeasure(COVERAGE, 0d))),
-      // docs with rating B
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 2d), newMeasure(NCLOC, 2000d), newMeasure(COVERAGE, 0d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 2d), newMeasure(NCLOC, 5000d), newMeasure(COVERAGE, 0d))),
-      // docs with rating C
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d), newMeasure(NCLOC, 20000d), newMeasure(COVERAGE, 0d))),
-      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d), newMeasure(NCLOC, 30000d), newMeasure(COVERAGE, 0d))),
-      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d), newMeasure(NCLOC, 40000d), newMeasure(COVERAGE, 0d))),
-      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d), newMeasure(NCLOC, 50000d), newMeasure(COVERAGE, 0d))),
-      // docs with rating D
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 4d), newMeasure(NCLOC, 120000d), newMeasure(COVERAGE, 0d))),
-      // docs with rating E
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d), newMeasure(NCLOC, 600000d), newMeasure(COVERAGE, 40d))),
-      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d), newMeasure(NCLOC, 700000d), newMeasure(COVERAGE, 50d))),
-      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d), newMeasure(NCLOC, 800000d), newMeasure(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();
-
-    // 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));
-    // 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));
-  }
-
-  @Test
-  public void facet_maintainability_rating_contains_only_projects_authorized_for_user() throws Exception {
-    userSession.login("john").setUserId(10);
-
-    // User can see these projects
-    addDocs(10L, null,
-      // 3 docs with rating A
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d))),
-      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d))),
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d))),
-      // 2 docs with rating B
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 2d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 2d))));
-
-    // User cannot see these projects
-    addDocs(33L, null,
-      // docs with rating C
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d))),
-      // docs with rating D
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 4d))),
-      // docs with rating E
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(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", 0L),
-      entry("4", 0L),
-      entry("5", 0L));
-  }
-
-  @Test
-  public void facet_reliability_rating() {
-    addDocs(
-      // 3 docs with rating A
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 1d))),
-      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 1d))),
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 1d))),
-      // 2 docs with rating B
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 2d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 2d))),
-      // 4 docs with rating C
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 3d))),
-      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 3d))),
-      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 3d))),
-      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 3d))),
-      // 2 docs with rating D
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 4d))),
-      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 4d))),
-      // 5 docs with rating E
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 5d))),
-      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 5d))),
-      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 5d))),
-      newDoc("P54", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 5d))),
-      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(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));
-  }
-
-  @Test
-  public void facet_security_rating() {
-    addDocs(
-      // 3 docs with rating A
-      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 1.0d))),
-      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 1.0d))),
-      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 1.0d))),
-      // 2 docs with rating B
-      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 2.0d))),
-      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 2.0d))),
-      // 4 docs with rating C
-      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 3.0d))),
-      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 3.0d))),
-      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 3.0d))),
-      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 3.0d))),
-      // 2 docs with rating D
-      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 4.0d))),
-      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 4.0d))),
-      // 5 docs with rating E
-      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 5.0d))),
-      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 5.0d))),
-      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 5.0d))),
-      newDoc("P54", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 5.0d))),
-      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(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));
-  }
-
-  @Test
-  public void facet_quality_gate() {
-    addDocs(
-      // 2 docs with QG OK
-      newDoc("P11", "K1", "N1").setQualityGate(OK.name()),
-      newDoc("P12", "K1", "N1").setQualityGate(OK.name()),
-      // 3 docs with QG WARN
-      newDoc("P21", "K1", "N1").setQualityGate(WARN.name()),
-      newDoc("P22", "K1", "N1").setQualityGate(WARN.name()),
-      newDoc("P23", "K1", "N1").setQualityGate(WARN.name()),
-      // 4 docs with QG ERROR
-      newDoc("P31", "K1", "N1").setQualityGate(ERROR.name()),
-      newDoc("P32", "K1", "N1").setQualityGate(ERROR.name()),
-      newDoc("P33", "K1", "N1").setQualityGate(ERROR.name()),
-      newDoc("P34", "K1", "N1").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));
-  }
-
-  @Test
-  public void facet_quality_gate_is_sticky() {
-    addDocs(
-      // 2 docs with QG OK
-      newDoc("P11", "K1", "N1").setQualityGate(OK.name()).setMeasures(newArrayList(newMeasure(NCLOC, 10d), newMeasure(COVERAGE, 0d))),
-      newDoc("P12", "K1", "N1").setQualityGate(OK.name()).setMeasures(newArrayList(newMeasure(NCLOC, 10d), newMeasure(COVERAGE, 0d))),
-      // 3 docs with QG WARN
-      newDoc("P21", "K1", "N1").setQualityGate(WARN.name()).setMeasures(newArrayList(newMeasure(NCLOC, 100d), newMeasure(COVERAGE, 0d))),
-      newDoc("P22", "K1", "N1").setQualityGate(WARN.name()).setMeasures(newArrayList(newMeasure(NCLOC, 100d), newMeasure(COVERAGE, 0d))),
-      newDoc("P23", "K1", "N1").setQualityGate(WARN.name()).setMeasures(newArrayList(newMeasure(NCLOC, 100d), newMeasure(COVERAGE, 0d))),
-      // 4 docs with QG ERROR
-      newDoc("P31", "K1", "N1").setQualityGate(ERROR.name()).setMeasures(newArrayList(newMeasure(NCLOC, 100d), newMeasure(COVERAGE, 0d))),
-      newDoc("P32", "K1", "N1").setQualityGate(ERROR.name()).setMeasures(newArrayList(newMeasure(NCLOC, 5000d), newMeasure(COVERAGE, 40d))),
-      newDoc("P33", "K1", "N1").setQualityGate(ERROR.name()).setMeasures(newArrayList(newMeasure(NCLOC, 12000d), newMeasure(COVERAGE, 50d))),
-      newDoc("P34", "K1", "N1").setQualityGate(ERROR.name()).setMeasures(newArrayList(newMeasure(NCLOC, 13000d), newMeasure(COVERAGE, 60d))));
-
-    Facets facets = underTest.search(new ProjectMeasuresQuery()
-      .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));
-    // 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));
-  }
-
-  @Test
-  public void facet_quality_gate_contains_only_projects_authorized_for_user() throws Exception {
-    userSession.login("john").setUserId(10);
-
-    // User can see these projects
-    addDocs(10L, null,
-      // 2 docs with QG OK
-      newDoc("P11", "K1", "N1").setQualityGate(OK.name()),
-      newDoc("P12", "K1", "N1").setQualityGate(OK.name()),
-      // 3 docs with QG WARN
-      newDoc("P21", "K1", "N1").setQualityGate(WARN.name()),
-      newDoc("P22", "K1", "N1").setQualityGate(WARN.name()),
-      newDoc("P23", "K1", "N1").setQualityGate(WARN.name()));
-
-    // User cannot see these projects
-    addDocs(33L, null,
-      // 4 docs with QG ERROR
-      newDoc("P31", "K1", "N1").setQualityGate(ERROR.name()),
-      newDoc("P32", "K1", "N1").setQualityGate(ERROR.name()),
-      newDoc("P33", "K1", "N1").setQualityGate(ERROR.name()),
-      newDoc("P34", "K1", "N1").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(), 0L),
-      entry(WARN.name(), 3L),
-      entry(OK.name(), 2L));
-  }
-
-  private void addDocs(ProjectMeasuresDoc... docs) {
-    addDocs(null, ANYONE, docs);
-  }
-
-  private void addDocs(@Nullable Long authorizeUser, @Nullable String authorizedGroup, ProjectMeasuresDoc... docs) {
-    try {
-      es.putDocuments(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, docs);
-      for (ProjectMeasuresDoc doc : docs) {
-        authorizationIndexerTester.indexProjectPermission(doc.getId(),
-          authorizedGroup != null ? singletonList(authorizedGroup) : emptyList(),
-          authorizeUser != null ? singletonList(authorizeUser) : emptyList());
-      }
-    } catch (Exception e) {
-      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, Object value) {
-    return ImmutableMap.of("key", key, "value", value);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresIndexerTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresIndexerTest.java
deleted file mode 100644 (file)
index c4fb24c..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.server.component.es;
-
-import java.util.Date;
-import org.elasticsearch.action.search.SearchRequestBuilder;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.config.MapSettings;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.permission.index.PermissionIndexerTester;
-
-import static java.util.Collections.emptyList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
-import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
-import static org.elasticsearch.index.query.QueryBuilders.termQuery;
-import static org.sonar.db.component.ComponentTesting.newProjectDto;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.TYPE_AUTHORIZATION;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
-
-public class ProjectMeasuresIndexerTest {
-
-  private System2 system2 = System2.INSTANCE;
-
-  @Rule
-  public EsTester esTester = new EsTester(new ProjectMeasuresIndexDefinition(new MapSettings()));
-
-  @Rule
-  public DbTester dbTester = DbTester.create(system2);
-
-  ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
-  PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(esTester);
-
-  ProjectMeasuresIndexer underTest = new ProjectMeasuresIndexer(system2, dbTester.getDbClient(), esTester.client());
-
-  @Test
-  public void index_nothing() {
-    underTest.index();
-
-    assertThat(esTester.countDocuments(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).isZero();
-  }
-
-  @Test
-  public void index_all_project() {
-    OrganizationDto organizationDto = dbTester.organizations().insert();
-    componentDbTester.insertProjectAndSnapshot(newProjectDto(organizationDto));
-    componentDbTester.insertProjectAndSnapshot(newProjectDto(organizationDto));
-    componentDbTester.insertProjectAndSnapshot(newProjectDto(organizationDto));
-
-    underTest.index();
-
-    assertThat(esTester.countDocuments(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).isEqualTo(3);
-  }
-
-  @Test
-  public void index_projects_even_when_no_analysis() {
-    ComponentDto project = componentDbTester.insertProject();
-
-    underTest.index();
-
-    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).containsOnly(project.uuid());
-  }
-
-  @Test
-  public void index_one_project() throws Exception {
-    OrganizationDto organizationDto = dbTester.organizations().insert();
-    ComponentDto project = newProjectDto(organizationDto);
-    componentDbTester.insertProjectAndSnapshot(project);
-    componentDbTester.insertProjectAndSnapshot(newProjectDto(organizationDto));
-
-    underTest.index(project.uuid());
-
-    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).containsOnly(project.uuid());
-  }
-
-  @Test
-  public void update_existing_document_when_indexing_one_project() throws Exception {
-    String uuid = "PROJECT-UUID";
-    esTester.putDocuments(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, new ProjectMeasuresDoc()
-      .setId(uuid)
-      .setKey("Old Key")
-      .setName("Old Name")
-      .setAnalysedAt(new Date(1_000_000L)));
-    ComponentDto project = newProjectDto(dbTester.getDefaultOrganization(), uuid).setKey("New key").setName("New name");
-    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
-
-    underTest.index(project.uuid());
-
-    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).containsOnly(uuid);
-    SearchRequestBuilder request = esTester.client()
-      .prepareSearch(INDEX_PROJECT_MEASURES)
-      .setTypes(TYPE_PROJECT_MEASURES)
-      .setQuery(boolQuery().must(matchAllQuery()).filter(
-        boolQuery()
-          .must(termQuery("_id", uuid))
-          .must(termQuery(ProjectMeasuresIndexDefinition.FIELD_KEY, "New key"))
-          .must(termQuery(ProjectMeasuresIndexDefinition.FIELD_NAME, "New name"))
-          .must(termQuery(ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT, new Date(analysis.getCreatedAt())))));
-    assertThat(request.get().getHits()).hasSize(1);
-  }
-
-  @Test
-  public void delete_project() {
-    OrganizationDto organizationDto = dbTester.organizations().insert();
-    ComponentDto project1 = newProjectDto(organizationDto);
-    componentDbTester.insertProjectAndSnapshot(project1);
-    ComponentDto project2 = newProjectDto(organizationDto);
-    componentDbTester.insertProjectAndSnapshot(project2);
-    ComponentDto project3 = newProjectDto(organizationDto);
-    componentDbTester.insertProjectAndSnapshot(project3);
-    underTest.index();
-    authorizationIndexerTester.indexProjectPermission(project1.uuid(), emptyList(), emptyList());
-    authorizationIndexerTester.indexProjectPermission(project2.uuid(), emptyList(), emptyList());
-    authorizationIndexerTester.indexProjectPermission(project3.uuid(), emptyList(), emptyList());
-
-    underTest.deleteProject(project1.uuid());
-
-    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).containsOnly(project2.uuid(), project3.uuid());
-    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_AUTHORIZATION)).containsOnly(project2.uuid(), project3.uuid());
-  }
-
-  @Test
-  public void does_nothing_when_deleting_unknown_project() throws Exception {
-    ComponentDto project = newProjectDto(dbTester.organizations().insert());
-    componentDbTester.insertProjectAndSnapshot(project);
-    underTest.index();
-    authorizationIndexerTester.indexProjectPermission(project.uuid(), emptyList(), emptyList());
-
-    underTest.deleteProject("UNKNOWN");
-
-    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).containsOnly(project.uuid());
-    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_AUTHORIZATION)).containsOnly(project.uuid());
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresQueryTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectMeasuresQueryTest.java
deleted file mode 100644 (file)
index d55f77e..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *  
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *  
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.server.component.es;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.measures.Metric.Level;
-
-import static org.assertj.core.api.Java6Assertions.assertThat;
-import static org.assertj.core.groups.Tuple.tuple;
-import static org.sonar.api.measures.Metric.Level.OK;
-import static org.sonar.server.component.es.ProjectMeasuresQuery.MetricCriterion;
-import static org.sonar.server.component.es.ProjectMeasuresQuery.Operator.EQ;
-
-public class ProjectMeasuresQueryTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  ProjectMeasuresQuery underTest = new ProjectMeasuresQuery();
-
-  @Test
-  public void empty_query() throws Exception {
-    assertThat(underTest.getMetricCriteria()).isEmpty();
-    assertThat(underTest.hasQualityGateStatus()).isFalse();
-  }
-
-  @Test
-  public void add_metric_criterion() throws Exception {
-    underTest.addMetricCriterion(new MetricCriterion("coverage", EQ, 10d));
-
-    assertThat(underTest.getMetricCriteria())
-      .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::getValue)
-      .containsOnly(tuple("coverage", EQ, 10d));
-  }
-
-  @Test
-  public void set_quality_gate_status() throws Exception {
-    underTest.setQualityGateStatus(OK);
-
-    assertThat(underTest.getQualityGateStatus()).isEqualTo(Level.OK);
-  }
-
-  @Test
-  public void fail_to_get_quality_gate_status_if_no_set() throws Exception {
-    expectedException.expect(IllegalStateException.class);
-    underTest.getQualityGateStatus();
-  }
-
-  @Test
-  public void fail_to_create_operator_from_unknown_value() throws Exception {
-    expectedException.expect(IllegalArgumentException.class);
-    ProjectMeasuresQuery.Operator.valueOf("UNKNOWN");
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectsEsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/es/ProjectsEsModuleTest.java
deleted file mode 100644 (file)
index 618e5f3..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.server.component.es;
-
-import org.junit.Test;
-import org.sonar.core.platform.ComponentContainer;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ProjectsEsModuleTest {
-  @Test
-  public void verify_count_of_added_components() {
-    ComponentContainer container = new ComponentContainer();
-    new ProjectsEsModule().configure(container);
-    assertThat(container.size()).isEqualTo(3 + 2);
-  }
-}
index d633d26ab7549e308f14f3fb8acc7db2b7a4f1df..032ac5d400414dc16c006d1bae96356da8c6cf69 100644 (file)
@@ -39,12 +39,12 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.component.ComponentService;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
index 22230e9175f976c114888ec5fbc953a4d87d41e6..ea80e3c427369d6215fceb69ae891b2c9a29cf2d 100644 (file)
@@ -23,16 +23,16 @@ package org.sonar.server.component.ws;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
-import org.sonar.server.component.es.ProjectMeasuresQuery;
+import org.sonar.server.measure.index.ProjectMeasuresQuery;
 import org.sonar.server.tester.UserSessionRule;
 
 import static java.util.Collections.emptySet;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
-import static org.sonar.server.component.es.ProjectMeasuresQuery.MetricCriterion;
-import static org.sonar.server.component.es.ProjectMeasuresQuery.Operator;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
 import static org.sonar.server.computation.task.projectanalysis.measure.Measure.Level.OK;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator;
 
 public class ProjectMeasuresQueryFactoryTest {
 
index 08f88fe7346966e0679b51370b249f1a5b53f79e..e727b54d31148e651d192b03b31bfa0ab1bcc670 100644 (file)
@@ -44,10 +44,10 @@ import org.sonar.db.component.ComponentDbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.property.PropertyDto;
-import org.sonar.server.component.es.ProjectMeasuresDoc;
-import org.sonar.server.component.es.ProjectMeasuresIndex;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
 import org.sonar.server.es.EsTester;
+import org.sonar.server.measure.index.ProjectMeasuresDoc;
+import org.sonar.server.measure.index.ProjectMeasuresIndex;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
 import org.sonar.server.permission.index.PermissionIndexerTester;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.KeyExamples;
@@ -72,8 +72,8 @@ import static org.sonar.db.component.ComponentTesting.newModuleDto;
 import static org.sonar.db.component.ComponentTesting.newProjectDto;
 import static org.sonar.db.component.ComponentTesting.newView;
 import static org.sonar.db.metric.MetricTesting.newMetricDto;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
-import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
 import static org.sonar.test.JsonAssert.assertJson;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_FILTER;
 
index d8f1a2c499cd1b9630532341ede31540392ec1e8..a00cd2423006a31dcbf2744cab52afa5c64f4c6e 100644 (file)
@@ -23,7 +23,7 @@ package org.sonar.server.computation.task.projectanalysis.step;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
new file mode 100644 (file)
index 0000000..cb5edbb
--- /dev/null
@@ -0,0 +1,897 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.IntStream;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.MapSettings;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.Facets;
+import org.sonar.server.es.SearchIdResult;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.measure.index.ProjectMeasuresDoc;
+import org.sonar.server.measure.index.ProjectMeasuresIndex;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresQuery;
+import org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+import org.sonar.server.measure.index.ProjectMeasuresQuery.Operator;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.tester.UserSessionRule;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Sets.newHashSet;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+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.Metric.Level.ERROR;
+import static org.sonar.api.measures.Metric.Level.OK;
+import static org.sonar.api.measures.Metric.Level.WARN;
+import static org.sonar.api.security.DefaultGroups.ANYONE;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+
+public class ProjectMeasuresIndexTest {
+
+  private static final String MAINTAINABILITY_RATING = "sqale_rating";
+  private static final String RELIABILITY_RATING = "reliability_rating";
+  private static final String SECURITY_RATING = "security_rating";
+  private static final String COVERAGE = "coverage";
+  private static final String DUPLICATION = "duplicated_lines_density";
+  private static final String NCLOC = "ncloc";
+
+  @Rule
+  public EsTester es = new EsTester(new ProjectMeasuresIndexDefinition(new MapSettings()));
+
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es);
+
+  private ProjectMeasuresIndex underTest = new ProjectMeasuresIndex(es.client(), userSession);
+
+  @Test
+  public void empty_search() {
+    List<String> result = underTest.search(new ProjectMeasuresQuery(), 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 ProjectMeasuresQuery(), new SearchOptions()).getIds();
+
+    assertThat(result).containsExactly("P2", "P3", "P1");
+  }
+
+  @Test
+  public void search_paginate_results() {
+    IntStream.rangeClosed(1, 9)
+      .forEach(i -> addDocs(newDoc("P" + i, "K" + i, "P" + i)));
+
+    SearchIdResult<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().setPage(2, 3));
+
+    assertThat(result.getIds()).containsExactly("P4", "P5", "P6");
+    assertThat(result.getTotal()).isEqualTo(9);
+  }
+
+  @Test
+  public void filter_with_lower_than() {
+    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))));
+
+    ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery()
+      .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LT, 80d));
+    List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
+
+    assertThat(result).containsExactly("P1");
+  }
+
+  @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))));
+
+    ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery()
+      .addMetricCriterion(new MetricCriterion(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, 30_000d))),
+      newDoc("P2", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 30_001d))),
+      newDoc("P3", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 30_001d))));
+
+    assertThat(underTest.search(new ProjectMeasuresQuery().addMetricCriterion(new MetricCriterion(NCLOC, Operator.GT, 30_000d)),
+      new SearchOptions()).getIds()).containsExactly("P2", "P3");
+    assertThat(underTest.search(new ProjectMeasuresQuery().addMetricCriterion(new MetricCriterion(NCLOC, Operator.GT, 100_000d)),
+      new SearchOptions()).getIds()).isEmpty();
+  }
+
+  @Test
+  public void filter_with_greater_than_or_equals() {
+    addDocs(
+      newDoc("P1", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 30_000d))),
+      newDoc("P2", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 30_001d))),
+      newDoc("P3", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 80d), newMeasure(NCLOC, 30_001d))));
+
+    assertThat(underTest.search(new ProjectMeasuresQuery().addMetricCriterion(new MetricCriterion(NCLOC, Operator.GTE, 30_001d)),
+      new SearchOptions()).getIds()).containsExactly("P2", "P3");
+    assertThat(underTest.search(new ProjectMeasuresQuery().addMetricCriterion(new MetricCriterion(NCLOC, Operator.GTE, 100_000d)),
+      new SearchOptions()).getIds()).isEmpty();
+  }
+
+  @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))));
+
+    ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery()
+      .addMetricCriterion(new MetricCriterion(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))));
+
+    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));
+    List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
+
+    assertThat(result).containsExactly("P2");
+  }
+
+  @Test
+  public void filter_on_quality_gate_status() {
+    addDocs(
+      newDoc("P1", "K1", "N1").setQualityGate("OK"),
+      newDoc("P2", "K2", "N2").setQualityGate("OK"),
+      newDoc("P3", "K3", "N3").setQualityGate("WARN"));
+    ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery().setQualityGateStatus(OK);
+
+    List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
+
+    assertThat(result).containsExactly("P1", "P2");
+  }
+
+  @Test
+  public void filter_on_ids() {
+    addDocs(
+      newDoc("P1", "K1", "N1"),
+      newDoc("P2", "K2", "N2"),
+      newDoc("P3", "K3", "N3"));
+    ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery().setProjectUuids(newHashSet("P1", "P3"));
+
+    List<String> result = underTest.search(esQuery, new SearchOptions()).getIds();
+
+    assertThat(result).containsExactly("P1", "P3");
+  }
+
+  @Test
+  public void return_only_projects_authorized_for_user() throws Exception {
+    userSession.login("john").setUserId(10);
+    addDocs(10L, null, newDoc("P1", "K1", "Windows"));
+    addDocs(10L, "dev", newDoc("P2", "K2", "apachee"));
+    addDocs(33L, null, newDoc("P10", "K10", "N10"));
+
+    List<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getIds();
+
+    assertThat(result).containsOnly("P1", "P2");
+  }
+
+  @Test
+  public void return_only_projects_authorized_for_user_groups() throws Exception {
+    userSession.setUserGroups("dev");
+    addDocs(10L, "dev", newDoc("P1", "K1", "apachee"));
+    addDocs(null, ANYONE, newDoc("P2", "K2", "N2"));
+    addDocs(null, "admin", newDoc("P10", "K10", "N10"));
+
+    List<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getIds();
+
+    assertThat(result).containsOnly("P1", "P2");
+  }
+
+  @Test
+  public void return_only_projects_authorized_for_user_and_groups() throws Exception {
+    userSession.login("john").setUserId(10).setUserGroups("dev");
+    addDocs(10L, null, newDoc("P1", "K1", "Windows"));
+    addDocs(null, "dev", newDoc("P2", "K2", "Apache"));
+    addDocs(10L, "dev", newDoc("P3", "K3", "apachee"));
+    // Current user is not able to see following projects
+    addDocs(null, "another group", newDoc("P5", "K5", "N5"));
+    addDocs(33L, null, newDoc("P6", "K6", "N6"));
+    addDocs((Long) null, null, newDoc("P7", "K7", "N7"));
+
+    List<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getIds();
+
+    assertThat(result).containsOnly("P1", "P2", "P3");
+  }
+
+  @Test
+  public void anyone_user_can_only_access_projects_authorized_for_anyone() throws Exception {
+    userSession.anonymous();
+    addDocs(null, ANYONE, newDoc("P1", "K1", "N1"));
+    addDocs(10L, null, newDoc("P2", "K2", "Windows"));
+    addDocs(null, "admin", newDoc("P3", "K3", "N3"));
+
+    List<String> result = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getIds();
+
+    assertThat(result).containsOnly("P1");
+  }
+
+  @Test
+  public void does_not_return_facet_when_no_facets_in_options() throws Exception {
+    addDocs(
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 10d), newMeasure(COVERAGE_KEY, 30d), newMeasure(MAINTAINABILITY_RATING, 3d)))
+        .setQualityGate(OK.name()));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions()).getFacets();
+
+    assertThat(facets.getAll()).isEmpty();
+  }
+
+  @Test
+  public void facet_ncloc() {
+    addDocs(
+      // 3 docs with ncloc<1K
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 0d))),
+      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 0d))),
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 999d))),
+      // 2 docs with ncloc>=1K and ncloc<10K
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 1_000d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 9_999d))),
+      // 4 docs with ncloc>=10K and ncloc<100K
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 10_000d))),
+      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 10_000d))),
+      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 11_000d))),
+      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 99_000d))),
+      // 2 docs with ncloc>=100K and ncloc<500K
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 100_000d))),
+      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 499_000d))),
+      // 5 docs with ncloc>= 500K
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 500_000d))),
+      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 100_000_000d))),
+      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 500_000d))),
+      newDoc("P54", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 1_000_000d))),
+      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(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));
+  }
+
+  @Test
+  public void facet_ncloc_is_sticky() {
+    addDocs(
+      // 1 docs with ncloc<1K
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 999d), newMeasure(COVERAGE, 0d), newMeasure(DUPLICATION, 0d))),
+      // 2 docs with ncloc>=1K and ncloc<10K
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 1_000d), newMeasure(COVERAGE, 10d), newMeasure(DUPLICATION, 0d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 9_999d), newMeasure(COVERAGE, 20d), newMeasure(DUPLICATION, 0d))),
+      // 3 docs with ncloc>=10K and ncloc<100K
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 10_000d), newMeasure(COVERAGE, 31d), newMeasure(DUPLICATION, 0d))),
+      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 11_000d), newMeasure(COVERAGE, 40d), newMeasure(DUPLICATION, 0d))),
+      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 99_000d), newMeasure(COVERAGE, 50d), newMeasure(DUPLICATION, 0d))),
+      // 2 docs with ncloc>=100K and ncloc<500K
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 100_000d), newMeasure(COVERAGE, 71d), newMeasure(DUPLICATION, 0d))),
+      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 499_000d), newMeasure(COVERAGE, 80d), newMeasure(DUPLICATION, 0d))),
+      // 1 docs with ncloc>= 500K
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 501_000d), newMeasure(COVERAGE, 81d), newMeasure(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();
+
+    // 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));
+    // 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));
+  }
+
+  @Test
+  public void facet_ncloc_contains_only_projects_authorized_for_user() throws Exception {
+    userSession.login("john").setUserId(10);
+
+    // User can see these projects
+    addDocs(10L, null,
+      // docs with ncloc<1K
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 0d))),
+      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 100d))),
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 999d))),
+      // docs with ncloc>=1K and ncloc<10K
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 1_000d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 9_999d))));
+
+    // User cannot see these projects
+    addDocs(33L, null,
+      // doc with ncloc>=10K and ncloc<100K
+      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 11_000d))),
+      // doc with ncloc>=100K and ncloc<500K
+      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 499_000d))),
+      // doc with ncloc>= 500K
+      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 501_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", 0L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
+  }
+
+  @Test
+  public void facet_coverage() {
+    addDocs(
+      // 3 docs with coverage<30%
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 0d))),
+      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 0d))),
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 29d))),
+      // 2 docs with coverage>=30% and coverage<50%
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 30d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 49d))),
+      // 4 docs with coverage>=50% and coverage<70%
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 50d))),
+      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 60d))),
+      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 60d))),
+      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 69d))),
+      // 2 docs with coverage>=70% and coverage<80%
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 70d))),
+      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 79d))),
+      // 5 docs with coverage>= 80%
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 80d))),
+      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 80d))),
+      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 90d))),
+      newDoc("P54", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 90.5d))),
+      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(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));
+  }
+
+  @Test
+  public void facet_coverage_is_sticky() {
+    addDocs(
+      // docs with coverage<30%
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 999d), newMeasure(COVERAGE, 0d), newMeasure(DUPLICATION, 0d))),
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 1_000d), newMeasure(COVERAGE, 10d), newMeasure(DUPLICATION, 0d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(NCLOC, 9_999d), newMeasure(COVERAGE, 20d), newMeasure(DUPLICATION, 0d))),
+      // docs with coverage>=30% and coverage<50%
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 10_000d), newMeasure(COVERAGE, 31d), newMeasure(DUPLICATION, 0d))),
+      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 11_000d), newMeasure(COVERAGE, 40d), newMeasure(DUPLICATION, 0d))),
+      // docs with coverage>=50% and coverage<70%
+      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 99_000d), newMeasure(COVERAGE, 50d), newMeasure(DUPLICATION, 0d))),
+      // docs with coverage>=70% and coverage<80%
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 100_000d), newMeasure(COVERAGE, 71d), newMeasure(DUPLICATION, 0d))),
+      // docs with coverage>= 80%
+      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 499_000d), newMeasure(COVERAGE, 80d), newMeasure(DUPLICATION, 15d))),
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(NCLOC, 501_000d), newMeasure(COVERAGE, 810d), newMeasure(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();
+
+    // 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));
+    // 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));
+  }
+
+  @Test
+  public void facet_coverage_contains_only_projects_authorized_for_user() throws Exception {
+    userSession.login("john").setUserId(10);
+
+    // User can see these projects
+    addDocs(10L, null,
+      // docs with coverage<30%
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 0d))),
+      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 0d))),
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(COVERAGE, 29d))),
+      // docs with coverage>=30% and coverage<50%
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 30d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(COVERAGE, 49d))));
+
+    // User cannot see these projects
+    addDocs(33L, null,
+      // docs with coverage>=50% and coverage<70%
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 50d))),
+      // docs with coverage>=70% and coverage<80%
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 70d))),
+      // docs with coverage>= 80%
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(COVERAGE, 80d))));
+
+    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));
+  }
+
+  @Test
+  public void facet_duplicated_lines_density() {
+    addDocs(
+      // 3 docs with duplication<3%
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 0d))),
+      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 0d))),
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 2.9d))),
+      // 2 docs with duplication>=3% and duplication<5%
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(DUPLICATION, 3d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(DUPLICATION, 4.9d))),
+      // 4 docs with duplication>=5% and duplication<10%
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 5d))),
+      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 6d))),
+      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 6d))),
+      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 9.9d))),
+      // 2 docs with duplication>=10% and duplication<20%
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 10d))),
+      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 19.9d))),
+      // 5 docs with duplication>= 20%
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 20d))),
+      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 20d))),
+      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 50d))),
+      newDoc("P54", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 80d))),
+      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(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));
+  }
+
+  @Test
+  public void facet_duplicated_lines_density_is_sticky() {
+    addDocs(
+      // docs with duplication<3%
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 0d), newMeasure(NCLOC, 999d), newMeasure(COVERAGE, 0d))),
+      // docs with duplication>=3% and duplication<5%
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(DUPLICATION, 3d), newMeasure(NCLOC, 5000d), newMeasure(COVERAGE, 0d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(DUPLICATION, 4.9d), newMeasure(NCLOC, 6000d), newMeasure(COVERAGE, 0d))),
+      // docs with duplication>=5% and duplication<10%
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 5d), newMeasure(NCLOC, 11000d), newMeasure(COVERAGE, 0d))),
+      // docs with duplication>=10% and duplication<20%
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 10d), newMeasure(NCLOC, 120000d), newMeasure(COVERAGE, 10d))),
+      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 19.9d), newMeasure(NCLOC, 130000d), newMeasure(COVERAGE, 20d))),
+      // docs with duplication>= 20%
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 20d), newMeasure(NCLOC, 1000000d), newMeasure(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();
+
+    // 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));
+    // 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));
+  }
+
+  @Test
+  public void facet_duplicated_lines_density_contains_only_projects_authorized_for_user() throws Exception {
+    userSession.login("john").setUserId(10);
+
+    // User can see these projects
+    addDocs(10L, null,
+      // docs with duplication<3%
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 0d))),
+      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 0d))),
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(DUPLICATION, 2.9d))),
+      // docs with duplication>=3% and duplication<5%
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(DUPLICATION, 3d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(DUPLICATION, 4.9d))));
+
+    // User cannot see these projects
+    addDocs(33L, null,
+      // docs with duplication>=5% and duplication<10%
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 5d))),
+      // docs with duplication>=10% and duplication<20%
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 10d))),
+      // docs with duplication>= 20%
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(DUPLICATION, 20d))));
+
+    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));
+  }
+
+  @Test
+  public void facet_maintainability_rating() {
+    addDocs(
+      // 3 docs with rating A
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d))),
+      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d))),
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d))),
+      // 2 docs with rating B
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 2d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 2d))),
+      // 4 docs with rating C
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d))),
+      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d))),
+      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d))),
+      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d))),
+      // 2 docs with rating D
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 4d))),
+      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 4d))),
+      // 5 docs with rating E
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d))),
+      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d))),
+      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d))),
+      newDoc("P54", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d))),
+      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(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));
+  }
+
+  @Test
+  public void facet_maintainability_rating_is_sticky() {
+    addDocs(
+      // docs with rating A
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d), newMeasure(NCLOC, 100d), newMeasure(COVERAGE, 0d))),
+      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d), newMeasure(NCLOC, 200d), newMeasure(COVERAGE, 0d))),
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d), newMeasure(NCLOC, 999d), newMeasure(COVERAGE, 0d))),
+      // docs with rating B
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 2d), newMeasure(NCLOC, 2000d), newMeasure(COVERAGE, 0d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 2d), newMeasure(NCLOC, 5000d), newMeasure(COVERAGE, 0d))),
+      // docs with rating C
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d), newMeasure(NCLOC, 20000d), newMeasure(COVERAGE, 0d))),
+      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d), newMeasure(NCLOC, 30000d), newMeasure(COVERAGE, 0d))),
+      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d), newMeasure(NCLOC, 40000d), newMeasure(COVERAGE, 0d))),
+      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d), newMeasure(NCLOC, 50000d), newMeasure(COVERAGE, 0d))),
+      // docs with rating D
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 4d), newMeasure(NCLOC, 120000d), newMeasure(COVERAGE, 0d))),
+      // docs with rating E
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d), newMeasure(NCLOC, 600000d), newMeasure(COVERAGE, 40d))),
+      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d), newMeasure(NCLOC, 700000d), newMeasure(COVERAGE, 50d))),
+      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 5d), newMeasure(NCLOC, 800000d), newMeasure(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();
+
+    // 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));
+    // 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));
+  }
+
+  @Test
+  public void facet_maintainability_rating_contains_only_projects_authorized_for_user() throws Exception {
+    userSession.login("john").setUserId(10);
+
+    // User can see these projects
+    addDocs(10L, null,
+      // 3 docs with rating A
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d))),
+      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d))),
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 1d))),
+      // 2 docs with rating B
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 2d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 2d))));
+
+    // User cannot see these projects
+    addDocs(33L, null,
+      // docs with rating C
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 3d))),
+      // docs with rating D
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(MAINTAINABILITY_RATING, 4d))),
+      // docs with rating E
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(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", 0L),
+      entry("4", 0L),
+      entry("5", 0L));
+  }
+
+  @Test
+  public void facet_reliability_rating() {
+    addDocs(
+      // 3 docs with rating A
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 1d))),
+      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 1d))),
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 1d))),
+      // 2 docs with rating B
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 2d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 2d))),
+      // 4 docs with rating C
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 3d))),
+      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 3d))),
+      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 3d))),
+      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 3d))),
+      // 2 docs with rating D
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 4d))),
+      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 4d))),
+      // 5 docs with rating E
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 5d))),
+      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 5d))),
+      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 5d))),
+      newDoc("P54", "K3", "N3").setMeasures(newArrayList(newMeasure(RELIABILITY_RATING, 5d))),
+      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(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));
+  }
+
+  @Test
+  public void facet_security_rating() {
+    addDocs(
+      // 3 docs with rating A
+      newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 1.0d))),
+      newDoc("P12", "K1", "N1").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 1.0d))),
+      newDoc("P13", "K1", "N1").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 1.0d))),
+      // 2 docs with rating B
+      newDoc("P21", "K2", "N2").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 2.0d))),
+      newDoc("P22", "K2", "N2").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 2.0d))),
+      // 4 docs with rating C
+      newDoc("P31", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 3.0d))),
+      newDoc("P32", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 3.0d))),
+      newDoc("P33", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 3.0d))),
+      newDoc("P34", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 3.0d))),
+      // 2 docs with rating D
+      newDoc("P41", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 4.0d))),
+      newDoc("P42", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 4.0d))),
+      // 5 docs with rating E
+      newDoc("P51", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 5.0d))),
+      newDoc("P52", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 5.0d))),
+      newDoc("P53", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 5.0d))),
+      newDoc("P54", "K3", "N3").setMeasures(newArrayList(newMeasure(SECURITY_RATING, 5.0d))),
+      newDoc("P55", "K3", "N3").setMeasures(newArrayList(newMeasure(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));
+  }
+
+  @Test
+  public void facet_quality_gate() {
+    addDocs(
+      // 2 docs with QG OK
+      newDoc("P11", "K1", "N1").setQualityGate(OK.name()),
+      newDoc("P12", "K1", "N1").setQualityGate(OK.name()),
+      // 3 docs with QG WARN
+      newDoc("P21", "K1", "N1").setQualityGate(WARN.name()),
+      newDoc("P22", "K1", "N1").setQualityGate(WARN.name()),
+      newDoc("P23", "K1", "N1").setQualityGate(WARN.name()),
+      // 4 docs with QG ERROR
+      newDoc("P31", "K1", "N1").setQualityGate(ERROR.name()),
+      newDoc("P32", "K1", "N1").setQualityGate(ERROR.name()),
+      newDoc("P33", "K1", "N1").setQualityGate(ERROR.name()),
+      newDoc("P34", "K1", "N1").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));
+  }
+
+  @Test
+  public void facet_quality_gate_is_sticky() {
+    addDocs(
+      // 2 docs with QG OK
+      newDoc("P11", "K1", "N1").setQualityGate(OK.name()).setMeasures(newArrayList(newMeasure(NCLOC, 10d), newMeasure(COVERAGE, 0d))),
+      newDoc("P12", "K1", "N1").setQualityGate(OK.name()).setMeasures(newArrayList(newMeasure(NCLOC, 10d), newMeasure(COVERAGE, 0d))),
+      // 3 docs with QG WARN
+      newDoc("P21", "K1", "N1").setQualityGate(WARN.name()).setMeasures(newArrayList(newMeasure(NCLOC, 100d), newMeasure(COVERAGE, 0d))),
+      newDoc("P22", "K1", "N1").setQualityGate(WARN.name()).setMeasures(newArrayList(newMeasure(NCLOC, 100d), newMeasure(COVERAGE, 0d))),
+      newDoc("P23", "K1", "N1").setQualityGate(WARN.name()).setMeasures(newArrayList(newMeasure(NCLOC, 100d), newMeasure(COVERAGE, 0d))),
+      // 4 docs with QG ERROR
+      newDoc("P31", "K1", "N1").setQualityGate(ERROR.name()).setMeasures(newArrayList(newMeasure(NCLOC, 100d), newMeasure(COVERAGE, 0d))),
+      newDoc("P32", "K1", "N1").setQualityGate(ERROR.name()).setMeasures(newArrayList(newMeasure(NCLOC, 5000d), newMeasure(COVERAGE, 40d))),
+      newDoc("P33", "K1", "N1").setQualityGate(ERROR.name()).setMeasures(newArrayList(newMeasure(NCLOC, 12000d), newMeasure(COVERAGE, 50d))),
+      newDoc("P34", "K1", "N1").setQualityGate(ERROR.name()).setMeasures(newArrayList(newMeasure(NCLOC, 13000d), newMeasure(COVERAGE, 60d))));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery()
+      .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));
+    // 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));
+  }
+
+  @Test
+  public void facet_quality_gate_contains_only_projects_authorized_for_user() throws Exception {
+    userSession.login("john").setUserId(10);
+
+    // User can see these projects
+    addDocs(10L, null,
+      // 2 docs with QG OK
+      newDoc("P11", "K1", "N1").setQualityGate(OK.name()),
+      newDoc("P12", "K1", "N1").setQualityGate(OK.name()),
+      // 3 docs with QG WARN
+      newDoc("P21", "K1", "N1").setQualityGate(WARN.name()),
+      newDoc("P22", "K1", "N1").setQualityGate(WARN.name()),
+      newDoc("P23", "K1", "N1").setQualityGate(WARN.name()));
+
+    // User cannot see these projects
+    addDocs(33L, null,
+      // 4 docs with QG ERROR
+      newDoc("P31", "K1", "N1").setQualityGate(ERROR.name()),
+      newDoc("P32", "K1", "N1").setQualityGate(ERROR.name()),
+      newDoc("P33", "K1", "N1").setQualityGate(ERROR.name()),
+      newDoc("P34", "K1", "N1").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(), 0L),
+      entry(WARN.name(), 3L),
+      entry(OK.name(), 2L));
+  }
+
+  private void addDocs(ProjectMeasuresDoc... docs) {
+    addDocs(null, ANYONE, docs);
+  }
+
+  private void addDocs(@Nullable Long authorizeUser, @Nullable String authorizedGroup, ProjectMeasuresDoc... docs) {
+    try {
+      es.putDocuments(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, docs);
+      for (ProjectMeasuresDoc doc : docs) {
+        authorizationIndexerTester.indexProjectPermission(doc.getId(),
+          authorizedGroup != null ? singletonList(authorizedGroup) : emptyList(),
+          authorizeUser != null ? singletonList(authorizeUser) : emptyList());
+      }
+    } catch (Exception e) {
+      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, Object value) {
+    return ImmutableMap.of("key", key, "value", value);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexerTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexerTest.java
new file mode 100644 (file)
index 0000000..a087830
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.measure.index;
+
+import java.util.Date;
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.MapSettings;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.measure.index.ProjectMeasuresDoc;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+
+import static java.util.Collections.emptyList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+import static org.sonar.db.component.ComponentTesting.newProjectDto;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_AUTHORIZATION;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+
+public class ProjectMeasuresIndexerTest {
+
+  private System2 system2 = System2.INSTANCE;
+
+  @Rule
+  public EsTester esTester = new EsTester(new ProjectMeasuresIndexDefinition(new MapSettings()));
+
+  @Rule
+  public DbTester dbTester = DbTester.create(system2);
+
+  ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
+  PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(esTester);
+
+  ProjectMeasuresIndexer underTest = new ProjectMeasuresIndexer(system2, dbTester.getDbClient(), esTester.client());
+
+  @Test
+  public void index_nothing() {
+    underTest.index();
+
+    assertThat(esTester.countDocuments(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).isZero();
+  }
+
+  @Test
+  public void index_all_project() {
+    OrganizationDto organizationDto = dbTester.organizations().insert();
+    componentDbTester.insertProjectAndSnapshot(newProjectDto(organizationDto));
+    componentDbTester.insertProjectAndSnapshot(newProjectDto(organizationDto));
+    componentDbTester.insertProjectAndSnapshot(newProjectDto(organizationDto));
+
+    underTest.index();
+
+    assertThat(esTester.countDocuments(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).isEqualTo(3);
+  }
+
+  @Test
+  public void index_projects_even_when_no_analysis() {
+    ComponentDto project = componentDbTester.insertProject();
+
+    underTest.index();
+
+    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).containsOnly(project.uuid());
+  }
+
+  @Test
+  public void index_one_project() throws Exception {
+    OrganizationDto organizationDto = dbTester.organizations().insert();
+    ComponentDto project = newProjectDto(organizationDto);
+    componentDbTester.insertProjectAndSnapshot(project);
+    componentDbTester.insertProjectAndSnapshot(newProjectDto(organizationDto));
+
+    underTest.index(project.uuid());
+
+    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).containsOnly(project.uuid());
+  }
+
+  @Test
+  public void update_existing_document_when_indexing_one_project() throws Exception {
+    String uuid = "PROJECT-UUID";
+    esTester.putDocuments(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES, new ProjectMeasuresDoc()
+      .setId(uuid)
+      .setKey("Old Key")
+      .setName("Old Name")
+      .setAnalysedAt(new Date(1_000_000L)));
+    ComponentDto project = newProjectDto(dbTester.getDefaultOrganization(), uuid).setKey("New key").setName("New name");
+    SnapshotDto analysis = componentDbTester.insertProjectAndSnapshot(project);
+
+    underTest.index(project.uuid());
+
+    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).containsOnly(uuid);
+    SearchRequestBuilder request = esTester.client()
+      .prepareSearch(INDEX_PROJECT_MEASURES)
+      .setTypes(TYPE_PROJECT_MEASURES)
+      .setQuery(boolQuery().must(matchAllQuery()).filter(
+        boolQuery()
+          .must(termQuery("_id", uuid))
+          .must(termQuery(ProjectMeasuresIndexDefinition.FIELD_KEY, "New key"))
+          .must(termQuery(ProjectMeasuresIndexDefinition.FIELD_NAME, "New name"))
+          .must(termQuery(ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT, new Date(analysis.getCreatedAt())))));
+    assertThat(request.get().getHits()).hasSize(1);
+  }
+
+  @Test
+  public void delete_project() {
+    OrganizationDto organizationDto = dbTester.organizations().insert();
+    ComponentDto project1 = newProjectDto(organizationDto);
+    componentDbTester.insertProjectAndSnapshot(project1);
+    ComponentDto project2 = newProjectDto(organizationDto);
+    componentDbTester.insertProjectAndSnapshot(project2);
+    ComponentDto project3 = newProjectDto(organizationDto);
+    componentDbTester.insertProjectAndSnapshot(project3);
+    underTest.index();
+    authorizationIndexerTester.indexProjectPermission(project1.uuid(), emptyList(), emptyList());
+    authorizationIndexerTester.indexProjectPermission(project2.uuid(), emptyList(), emptyList());
+    authorizationIndexerTester.indexProjectPermission(project3.uuid(), emptyList(), emptyList());
+
+    underTest.deleteProject(project1.uuid());
+
+    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).containsOnly(project2.uuid(), project3.uuid());
+    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_AUTHORIZATION)).containsOnly(project2.uuid(), project3.uuid());
+  }
+
+  @Test
+  public void does_nothing_when_deleting_unknown_project() throws Exception {
+    ComponentDto project = newProjectDto(dbTester.organizations().insert());
+    componentDbTester.insertProjectAndSnapshot(project);
+    underTest.index();
+    authorizationIndexerTester.indexProjectPermission(project.uuid(), emptyList(), emptyList());
+
+    underTest.deleteProject("UNKNOWN");
+
+    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURES)).containsOnly(project.uuid());
+    assertThat(esTester.getIds(INDEX_PROJECT_MEASURES, TYPE_AUTHORIZATION)).containsOnly(project.uuid());
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresQueryTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresQueryTest.java
new file mode 100644 (file)
index 0000000..e615ea4
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *  
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *  
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.measure.index;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.measures.Metric.Level;
+import org.sonar.server.measure.index.ProjectMeasuresQuery;
+
+import static org.assertj.core.api.Java6Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.sonar.api.measures.Metric.Level.OK;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator.EQ;
+
+public class ProjectMeasuresQueryTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  ProjectMeasuresQuery underTest = new ProjectMeasuresQuery();
+
+  @Test
+  public void empty_query() throws Exception {
+    assertThat(underTest.getMetricCriteria()).isEmpty();
+    assertThat(underTest.hasQualityGateStatus()).isFalse();
+  }
+
+  @Test
+  public void add_metric_criterion() throws Exception {
+    underTest.addMetricCriterion(new MetricCriterion("coverage", EQ, 10d));
+
+    assertThat(underTest.getMetricCriteria())
+      .extracting(MetricCriterion::getMetricKey, MetricCriterion::getOperator, MetricCriterion::getValue)
+      .containsOnly(tuple("coverage", EQ, 10d));
+  }
+
+  @Test
+  public void set_quality_gate_status() throws Exception {
+    underTest.setQualityGateStatus(OK);
+
+    assertThat(underTest.getQualityGateStatus()).isEqualTo(Level.OK);
+  }
+
+  @Test
+  public void fail_to_get_quality_gate_status_if_no_set() throws Exception {
+    expectedException.expect(IllegalStateException.class);
+    underTest.getQualityGateStatus();
+  }
+
+  @Test
+  public void fail_to_create_operator_from_unknown_value() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+    ProjectMeasuresQuery.Operator.valueOf("UNKNOWN");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectsEsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectsEsModuleTest.java
new file mode 100644 (file)
index 0000000..037529e
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.measure.index;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+import org.sonar.server.measure.index.ProjectsEsModule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProjectsEsModuleTest {
+  @Test
+  public void verify_count_of_added_components() {
+    ComponentContainer container = new ComponentContainer();
+    new ProjectsEsModule().configure(container);
+    assertThat(container.size()).isEqualTo(3 + 2);
+  }
+}
index fbd43946d43d44a898a151b41dad4093737b6564..f469e8d64da6ee32085b35e2420eb62d506e432d 100644 (file)
@@ -34,9 +34,9 @@ import org.sonar.db.permission.GroupPermissionDto;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDbTester;
 import org.sonar.db.user.UserDto;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.issue.index.IssueIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
 
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
index 70c8ef952f3276bfacc6db0059bedf2b23a59df5..9793ec14e90fa0a9e4ab9b07b2792b6391af6958 100644 (file)
@@ -23,9 +23,9 @@ package org.sonar.server.permission.index;
 import java.util.List;
 import org.elasticsearch.action.search.SearchRequestBuilder;
 import org.elasticsearch.index.query.BoolQueryBuilder;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.issue.index.IssueIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
 
 import static java.util.Collections.emptyList;
 import static org.assertj.core.api.Assertions.assertThat;
index 2e026aff246034f6246ce65166b15df349663f9c..1dd8b847cf41f1561176e70a871bf61cc8251ad3 100644 (file)
@@ -34,12 +34,12 @@ import org.sonar.db.permission.PermissionQuery;
 import org.sonar.db.permission.template.PermissionTemplateDto;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDto;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.issue.index.IssueIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
 import org.sonar.server.permission.PermissionTemplateService;
 import org.sonar.server.permission.index.PermissionIndexer;
 import org.sonar.server.permission.index.PermissionIndexerTester;
index 9bf31c4f3b781aec00eac442e91766539a3d5ef8..f797ed0a32ade4fac0f07e41c40b22c1c0a464cc 100644 (file)
@@ -25,11 +25,11 @@ import org.sonar.api.config.MapSettings;
 import org.sonar.api.utils.System2;
 import org.sonar.db.DbTester;
 import org.sonar.db.rule.RuleTesting;
-import org.sonar.server.component.es.ProjectMeasuresDoc;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.issue.IssueDocTesting;
 import org.sonar.server.issue.index.IssueIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresDoc;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
 import org.sonar.server.rule.index.RuleDoc;
 import org.sonar.server.rule.index.RuleIndexDefinition;
 import org.sonar.server.view.index.ViewDoc;
index fcb98e3d24cf637660e09f27fad5da8f36e247b9..f28e09ec990cdda0f21fe199c04f6d22b289c516 100644 (file)
@@ -45,13 +45,13 @@ import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleTesting;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.component.ComponentFinder;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.issue.IssueDocTesting;
 import org.sonar.server.issue.index.IssueAuthorizationDoc;
 import org.sonar.server.issue.index.IssueIndexDefinition;
 import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.test.index.TestDoc;
 import org.sonar.server.test.index.TestIndexDefinition;
 import org.sonar.server.test.index.TestIndexer;
index 8e680f833b7eddad198a6c24839d4ea0e64f8287..732a1cf55a2396f8b7a1693135e8ff569f78f720 100644 (file)
@@ -43,13 +43,13 @@ import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleTesting;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.component.ComponentFinder;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.issue.IssueDocTesting;
 import org.sonar.server.issue.index.IssueAuthorizationDoc;
 import org.sonar.server.issue.index.IssueIndexDefinition;
 import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.test.index.TestDoc;
 import org.sonar.server.test.index.TestIndexDefinition;
 import org.sonar.server.test.index.TestIndexer;
index d1a34076e89c689bf897981540ac1d9bb3ce8983..7356d8f8ddfc85f01527618905673bb944e8a40b 100644 (file)
@@ -35,10 +35,10 @@ import org.sonar.db.qualityprofile.QualityProfileDbTester;
 import org.sonar.db.qualityprofile.QualityProfileDto;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.component.ComponentService;
-import org.sonar.server.component.es.ProjectMeasuresIndexDefinition;
-import org.sonar.server.component.es.ProjectMeasuresIndexer;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.language.LanguageTesting;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
 import org.sonar.server.qualityprofile.QProfileLookup;
 import org.sonar.server.qualityprofile.QProfileName;
 import org.sonar.server.qualityprofile.QProfileProjectOperations;