diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2016-10-24 15:39:26 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2016-10-24 15:39:26 +0200 |
commit | 7fbdae0b91dd971fe4f5f5991987369319faf3de (patch) | |
tree | 804aab8bdfc75df7366d623e6e4dc9a512448481 /server/sonar-server | |
parent | be7fe2686f3c4cb62c5351e81f6a51d3983e8547 (diff) | |
download | sonarqube-7fbdae0b91dd971fe4f5f5991987369319faf3de.tar.gz sonarqube-7fbdae0b91dd971fe4f5f5991987369319faf3de.zip |
SONAR-8323 api/components/search_projects should return "sticky" facets
Diffstat (limited to 'server/sonar-server')
2 files changed, 434 insertions, 39 deletions
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 index f6767705457..00368bc78a1 100644 --- 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 @@ -19,8 +19,12 @@ */ 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; @@ -37,6 +41,7 @@ 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; @@ -45,6 +50,7 @@ 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; @@ -76,29 +82,54 @@ public class ProjectMeasuresIndex extends BaseIndex { } public SearchIdResult<String> search(ProjectMeasuresQuery query, SearchOptions searchOptions) { - QueryBuilder esQuery = createEsQuery(query); - - SearchRequestBuilder request = getClient() + SearchRequestBuilder requestBuilder = getClient() .prepareSearch(INDEX_PROJECT_MEASURES) .setTypes(TYPE_PROJECT_MEASURES) .setFetchSource(false) - .setQuery(esQuery) .setFrom(searchOptions.getOffset()) .setSize(searchOptions.getLimit()) - .addAggregation(createQualityGateFacet()) - .addAggregation(createRatingFacet(SQALE_RATING_KEY)) - .addAggregation(createRatingFacet(RELIABILITY_RATING_KEY)) - .addAggregation(createRatingFacet(SECURITY_RATING_KEY)) - .addAggregation(createRangeFacet(DUPLICATED_LINES_DENSITY_KEY, ImmutableList.of(3d, 5d, 10d, 20d))) - .addAggregation(createRangeFacet(COVERAGE_KEY, ImmutableList.of(30d, 50d, 70d, 80d))) - .addAggregation(createRangeFacet(NCLOC_KEY, ImmutableList.of(1_000d, 10_000d, 100_000d, 500_000d))) .addSort(FIELD_NAME + "." + SORT_SUFFIX, SortOrder.ASC); - return new SearchIdResult<>(request.get(), id -> id); + BoolQueryBuilder esFilter = boolQuery(); + Map<String, QueryBuilder> filters = createFilters(query); + filters.values().forEach(esFilter::must); + requestBuilder.setQuery(esFilter); + + addFacets(requestBuilder, filters); + return new SearchIdResult<>(requestBuilder.get(), id -> id); + } + + private static void addFacets(SearchRequestBuilder esSearch, Map<String, QueryBuilder> filters) { + addRangeFacet(esSearch, NCLOC_KEY, ImmutableList.of(1_000d, 10_000d, 100_000d, 500_000d), filters); + addRangeFacet(esSearch, DUPLICATED_LINES_DENSITY_KEY, ImmutableList.of(3d, 5d, 10d, 20d), filters); + addRangeFacet(esSearch, COVERAGE_KEY, ImmutableList.of(30d, 50d, 70d, 80d), filters); + addRatingFacet(esSearch, SQALE_RATING_KEY, filters); + addRatingFacet(esSearch, RELIABILITY_RATING_KEY, filters); + addRatingFacet(esSearch, SECURITY_RATING_KEY, filters); + 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); + RangeBuilder rangeAgg = AggregationBuilders.range(metricKey) + .field(FIELD_VALUE); final int lastIndex = thresholds.size() - 1; IntStream.range(0, thresholds.size()) .forEach(i -> { @@ -126,7 +157,7 @@ public class ProjectMeasuresIndex extends BaseIndex { .subAggregation( AggregationBuilders.filter("filter_" + metricKey) .filter(termsQuery(FIELD_KEY, metricKey)) - .subAggregation(AggregationBuilders.filters(metricKey) + .subAggregation(filters(metricKey) .filter("1", termQuery(FIELD_VALUE, 1d)) .filter("2", termQuery(FIELD_VALUE, 2d)) .filter("3", termQuery(FIELD_VALUE, 3d)) @@ -141,16 +172,24 @@ public class ProjectMeasuresIndex extends BaseIndex { .filter(Metric.Level.OK.name(), termQuery(FIELD_QUALITY_GATE, Metric.Level.OK.name())); } - private QueryBuilder createEsQuery(ProjectMeasuresQuery query) { - BoolQueryBuilder filters = boolQuery() - .must(createAuthorizationFilter()); - query.getMetricCriteria().stream() - .map(criterion -> nestedQuery(FIELD_MEASURES, boolQuery() - .filter(termQuery(FIELD_KEY, criterion.getMetricKey())) - .filter(toValueQuery(criterion)))) - .forEach(filters::filter); + 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.filter(termQuery(FIELD_QUALITY_GATE, query.getQualityGateStatus().name())); + filters.put(ALERT_STATUS_KEY, termQuery(FIELD_QUALITY_GATE, query.getQualityGateStatus().name())); } return filters; } 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 index b5c07e0d4bc..2ea96edc3cd 100644 --- 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 @@ -30,7 +30,6 @@ import javax.annotation.Nullable; import org.junit.Rule; import org.junit.Test; import org.sonar.api.config.MapSettings; -import org.sonar.api.measures.Metric.Level; import org.sonar.server.component.es.ProjectMeasuresQuery.MetricCriterion; import org.sonar.server.component.es.ProjectMeasuresQuery.Operator; import org.sonar.server.es.EsTester; @@ -45,7 +44,9 @@ 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.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; @@ -175,7 +176,8 @@ public class ProjectMeasuresIndexTest { ProjectMeasuresQuery esQuery = new ProjectMeasuresQuery() .addMetricCriterion(new MetricCriterion(COVERAGE, Operator.LTE, 80d)) - .addMetricCriterion(new MetricCriterion(NCLOC, Operator.GT, 10_000d)); + .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"); @@ -250,7 +252,7 @@ public class ProjectMeasuresIndexTest { public void facet_ncloc() { addDocs( // 3 docs with ncloc<1K - newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 0d))), + newDoc("P11", "K1", "N1").setMeasures(newArrayList(newMeasure(NCLOC, 0d), newMeasure(COVERAGE, 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 @@ -282,6 +284,78 @@ public class ProjectMeasuresIndexTest { } @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()).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()).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% @@ -317,6 +391,78 @@ public class ProjectMeasuresIndexTest { } @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()).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()).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% @@ -352,6 +498,76 @@ public class ProjectMeasuresIndexTest { } @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()).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()).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 @@ -387,6 +603,82 @@ public class ProjectMeasuresIndexTest { } @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()).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()).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 @@ -460,25 +752,89 @@ public class ProjectMeasuresIndexTest { public void facet_quality_gate() { addDocs( // 2 docs with QG OK - newDoc("P11", "K1", "N1").setQualityGate(Level.OK.name()), - newDoc("P12", "K1", "N1").setQualityGate(Level.OK.name()), + 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()).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(Level.WARN.name()), - newDoc("P22", "K1", "N1").setQualityGate(Level.WARN.name()), - newDoc("P23", "K1", "N1").setQualityGate(Level.WARN.name()), + 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()).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(Level.ERROR.name()), - newDoc("P32", "K1", "N1").setQualityGate(Level.ERROR.name()), - newDoc("P33", "K1", "N1").setQualityGate(Level.ERROR.name()), - newDoc("P34", "K1", "N1").setQualityGate(Level.ERROR.name())); + 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()).getFacets().get(ALERT_STATUS_KEY); assertThat(result).containsExactly( - entry(Level.ERROR.name(), 4L), - entry(Level.WARN.name(), 3L), - entry(Level.OK.name(), 2L) - ); + entry(ERROR.name(), 0L), + entry(WARN.name(), 3L), + entry(OK.name(), 2L)); } private void addDocs(ProjectMeasuresDoc... docs) { |