From 12c522be617375b10883b205b203fd3522b79940 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 25 Aug 2023 14:55:45 +0200 Subject: [PATCH] SONAR-20021, SONAR-20198 Fix CCT filtering and facets when both attributes of nested field provided --- .../sonar/server/rule/index/RuleIndexIT.java | 34 +++++++- .../sonar/server/es/StickyFacetBuilder.java | 4 +- .../sonar/server/rule/index/RuleIndex.java | 79 +++++++++++++------ .../sonar/server/issue/index/IssueIndex.java | 30 ++++--- .../issue/index/IssueIndexFacetsTest.java | 32 ++++++++ 5 files changed, 139 insertions(+), 40 deletions(-) diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java index 32f58812bbc..85bab8dbf45 100644 --- a/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java +++ b/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java @@ -519,7 +519,7 @@ public class RuleIndexIT { List rules = new ArrayList<>(); - //Creation of one rule for each standard security category defined (except other) + // Creation of one rule for each standard security category defined (except other) for (Map.Entry> sqCategorySetEntry : SecurityStandards.CWES_BY_SQ_CATEGORY.entrySet()) { rules.add(createRule(setSecurityStandards(of("cwe:" + sqCategorySetEntry.getValue().iterator().next())), r -> r.setType(SECURITY_HOTSPOT))); } @@ -797,7 +797,6 @@ public class RuleIndexIT { SearchIdResult result1 = underTest.search(query, new SearchOptions()); assertThat(result1.getUuids()).isEmpty(); - query = new RuleQuery(); query.setCleanCodeAttributesCategories(List.of(CleanCodeAttribute.FOCUSED.getAttributeCategory().name())); @@ -816,7 +815,6 @@ public class RuleIndexIT { SearchIdResult result1 = underTest.search(query.setImpactSoftwareQualities(List.of(SoftwareQuality.MAINTAINABILITY.name())), new SearchOptions()); assertThat(result1.getUuids()).isEmpty(); - query = new RuleQuery(); SearchIdResult result2 = underTest.search(query.setImpactSoftwareQualities(List.of(SoftwareQuality.SECURITY.name())), new SearchOptions()); assertThat(result2.getUuids()).containsOnly(phpRule.getUuid()); @@ -832,7 +830,6 @@ public class RuleIndexIT { SearchIdResult result1 = underTest.search(query.setImpactSeverities(List.of(Severity.MEDIUM.name())), new SearchOptions()); assertThat(result1.getUuids()).isEmpty(); - query = new RuleQuery(); SearchIdResult result2 = underTest.search(query.setImpactSeverities(List.of(Severity.HIGH.name())), new SearchOptions()); assertThat(result2.getUuids()).containsOnly(phpRule.getUuid()); @@ -908,6 +905,35 @@ public class RuleIndexIT { entry("RELIABILITY", 0L)); } + @Test + public void search_whenFilteringOnSeverityAndSoftwareQuality_shouldReturnFacet() { + ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.HIGH).setUuid("uuid"); + ImpactDto impactDto2 = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.LOW).setUuid("uuid2"); + ImpactDto impactDto3 = new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.LOW).setUuid("uuid3"); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto2, impactDto3))); + index(); + + RuleQuery query = new RuleQuery(); + + SearchIdResult result = underTest.search( + query.setImpactSeverities(Set.of(Severity.LOW.name())).setImpactSoftwareQualities(List.of(SoftwareQuality.MAINTAINABILITY.name())), + new SearchOptions().addFacets(List.of("impactSoftwareQualities", "impactSeverities"))); + + assertThat(result.getFacets().getAll()).hasSize(2); + assertThat(result.getFacets().getAll().get("impactSoftwareQualities")) + .containsOnly( + entry("SECURITY", 0L), + entry("MAINTAINABILITY", 1L), + entry("RELIABILITY", 1L)); + + assertThat(result.getFacets().getAll().get("impactSeverities")) + .containsOnly( + entry("HIGH", 1L), + entry("MEDIUM", 0L), + entry("LOW", 1L)); + } + @Test public void search_should_support_severity_facet() { ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH).setUuid("uuid"); diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/es/StickyFacetBuilder.java b/server/sonar-server-common/src/main/java/org/sonar/server/es/StickyFacetBuilder.java index 798de162480..d84c469860d 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/es/StickyFacetBuilder.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/es/StickyFacetBuilder.java @@ -100,8 +100,8 @@ public class StickyFacetBuilder { .subAggregation(facetTopAggregation); } - public AggregationBuilder buildTopAggregationStickyFacet(String fieldName, String facetName, AggregationBuilder additionalAggregationFilter) { - BoolQueryBuilder facetFilter = getStickyFacetFilter(fieldName); + public AggregationBuilder buildNestedAggregationStickyFacet(String parentFieldName, String childFieldName, String facetName, AggregationBuilder additionalAggregationFilter) { + BoolQueryBuilder facetFilter = getStickyFacetFilter(parentFieldName + "." + childFieldName, parentFieldName); return AggregationBuilders .global(facetName) .subAggregation(AggregationBuilders diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java index fab8e5dfd4c..61b3207172e 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java @@ -92,6 +92,7 @@ import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.ENGLISH_H import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER; import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_WORDS_ANALYZER; import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER; +import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACTS; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_INHERITANCE; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_PROFILE_UUID; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_SEVERITY; @@ -120,6 +121,8 @@ import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_TAGS; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_TEMPLATE_KEY; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_TYPE; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_UPDATED_AT; +import static org.sonar.server.rule.index.RuleIndexDefinition.SUB_FIELD_SEVERITY; +import static org.sonar.server.rule.index.RuleIndexDefinition.SUB_FIELD_SOFTWARE_QUALITY; import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE; import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE; @@ -236,9 +239,9 @@ public class RuleIndex { BoolQueryBuilder textQuery = boolQuery(); JavaTokenizer.split(queryText) .stream().map(token -> boolQuery().should( - matchQuery( - SEARCH_GRAMS_ANALYZER.subField(FIELD_RULE_NAME), - StringUtils.left(token, DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH)).boost(20F)) + matchQuery( + SEARCH_GRAMS_ANALYZER.subField(FIELD_RULE_NAME), + StringUtils.left(token, DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH)).boost(20F)) .should( matchPhraseQuery( ENGLISH_HTML_ANALYZER.subField(FIELD_RULE_HTML_DESCRIPTION), @@ -256,7 +259,7 @@ public class RuleIndex { private static QueryBuilder termQuery(String field, String query, float boost) { return QueryBuilders.multiMatchQuery(query, - field, SEARCH_WORDS_ANALYZER.subField(field)) + field, SEARCH_WORDS_ANALYZER.subField(field)) .operator(Operator.AND) .boost(boost); } @@ -375,23 +378,32 @@ public class RuleIndex { } private static void addImpactFilters(RuleQuery query, Map allFilters) { - if (isNotEmpty(query.getImpactSoftwareQualities())) { + if (isEmpty(query.getImpactSoftwareQualities()) && isEmpty(query.getImpactSeverities())) { + return; + } + if (isNotEmpty(query.getImpactSoftwareQualities()) && isEmpty(query.getImpactSeverities())) { allFilters.put( FIELD_RULE_IMPACT_SOFTWARE_QUALITY, nestedQuery( FIELD_RULE_IMPACTS, termsQuery(FIELD_RULE_IMPACT_SOFTWARE_QUALITY, query.getImpactSoftwareQualities()), ScoreMode.Avg)); + return; } - if (isNotEmpty(query.getImpactSeverities())) { + if (isNotEmpty(query.getImpactSeverities()) && isEmpty(query.getImpactSoftwareQualities())) { allFilters.put( FIELD_RULE_IMPACT_SEVERITY, nestedQuery( FIELD_RULE_IMPACTS, termsQuery(FIELD_RULE_IMPACT_SEVERITY, query.getImpactSeverities()), ScoreMode.Avg)); + return; } + BoolQueryBuilder impactsFilter = boolQuery() + .filter(termsQuery(FIELD_RULE_IMPACT_SOFTWARE_QUALITY, query.getImpactSoftwareQualities())) + .filter(termsQuery(FIELD_RULE_IMPACT_SEVERITY, query.getImpactSeverities())); + allFilters.put(FIELD_RULE_IMPACTS, nestedQuery(FIELD_ISSUE_IMPACTS, impactsFilter, ScoreMode.Avg)); } private static void addSecurityStandardFilter(Map filters, String key, Collection values) { @@ -524,52 +536,67 @@ public class RuleIndex { return; } - Function mainQuery = softwareQuality -> boolQuery() - .filter(QueryBuilders.termQuery(FIELD_RULE_IMPACT_SOFTWARE_QUALITY, softwareQuality.name())); + Function mainQuery = softwareQuality -> boolQuery() + .filter(QueryBuilders.termQuery(FIELD_RULE_IMPACT_SOFTWARE_QUALITY, softwareQuality)); FiltersAggregator.KeyedFilter[] keyedFilters = Arrays.stream(SoftwareQuality.values()) .map(softwareQuality -> new FiltersAggregator.KeyedFilter(softwareQuality.name(), - isNotEmpty(query.getImpactSeverities()) ? mainQuery.apply(softwareQuality) - .filter(termsQuery(FIELD_RULE_IMPACT_SEVERITY, query.getImpactSeverities())) : mainQuery.apply(softwareQuality))) + buildSoftwareQualityFacetFilter(query, mainQuery, softwareQuality.name()))) .toArray(FiltersAggregator.KeyedFilter[]::new); NestedAggregationBuilder nestedAggregationBuilder = AggregationBuilders.nested("nested_" + FACET_IMPACT_SOFTWARE_QUALITY, FIELD_RULE_IMPACTS) .subAggregation(filters(FACET_IMPACT_SOFTWARE_QUALITY, keyedFilters)); - AggregationBuilder aggregationBuilder = stickyFacetBuilder.buildTopAggregationStickyFacet(FIELD_RULE_IMPACT_SOFTWARE_QUALITY, FACET_IMPACT_SOFTWARE_QUALITY, - nestedAggregationBuilder); + AggregationBuilder aggregationBuilder = stickyFacetBuilder.buildNestedAggregationStickyFacet(FIELD_RULE_IMPACTS, SUB_FIELD_SOFTWARE_QUALITY, + FACET_IMPACT_SOFTWARE_QUALITY, nestedAggregationBuilder); aggregations.put(FACET_IMPACT_SOFTWARE_QUALITY, aggregationBuilder); } + private static BoolQueryBuilder buildSoftwareQualityFacetFilter(RuleQuery query, Function mainQuery, String value) { + BoolQueryBuilder boolQueryBuilder = mainQuery.apply(value); + if (isNotEmpty(query.getImpactSeverities())) { + return boolQueryBuilder.filter(termsQuery(FIELD_RULE_IMPACT_SEVERITY, query.getImpactSeverities())); + } + return boolQueryBuilder; + } + private static void addImpactSeverityFacetIfNeeded(SearchOptions options, RuleQuery query, Map aggregations, StickyFacetBuilder stickyFacetBuilder) { if (!options.getFacets().contains(FACET_IMPACT_SEVERITY)) { return; } - Function mainQuery = softwareQuality -> boolQuery() - .filter(QueryBuilders.termQuery(FIELD_RULE_IMPACT_SEVERITY, softwareQuality.name())); + Function mainQuery = severity -> boolQuery() + .filter(QueryBuilders.termQuery(FIELD_RULE_IMPACT_SEVERITY, severity)); FiltersAggregator.KeyedFilter[] keyedFilters = Arrays.stream(org.sonar.api.issue.impact.Severity.values()) .map(severity -> new FiltersAggregator.KeyedFilter(severity.name(), - isNotEmpty(query.getImpactSoftwareQualities()) ? mainQuery.apply(severity).filter(termsQuery(FIELD_RULE_IMPACT_SOFTWARE_QUALITY, query.getImpactSoftwareQualities())) - : mainQuery.apply(severity))) + buildSeverityFacetFilter(query, mainQuery, severity.name()))) .toArray(FiltersAggregator.KeyedFilter[]::new); NestedAggregationBuilder nestedAggregationBuilder = AggregationBuilders.nested("nested_" + FACET_IMPACT_SEVERITY, FIELD_RULE_IMPACTS) .subAggregation(filters(FACET_IMPACT_SEVERITY, keyedFilters).subAggregation(reverseNested("reverse_nested_" + FIELD_RULE_IMPACT_SEVERITY))); - AggregationBuilder aggregationBuilder = stickyFacetBuilder.buildTopAggregationStickyFacet(FIELD_RULE_IMPACT_SEVERITY, FACET_IMPACT_SEVERITY, nestedAggregationBuilder); + AggregationBuilder aggregationBuilder = stickyFacetBuilder.buildNestedAggregationStickyFacet(FIELD_RULE_IMPACTS, SUB_FIELD_SEVERITY, + FACET_IMPACT_SEVERITY, nestedAggregationBuilder); aggregations.put(FACET_IMPACT_SEVERITY, aggregationBuilder); } + private static BoolQueryBuilder buildSeverityFacetFilter(RuleQuery query, Function mainQuery, String value) { + BoolQueryBuilder boolQueryBuilder = mainQuery.apply(value); + if (isNotEmpty(query.getImpactSoftwareQualities())) { + return boolQueryBuilder.filter(termsQuery(FIELD_RULE_IMPACT_SOFTWARE_QUALITY, query.getImpactSoftwareQualities())); + } + return boolQueryBuilder; + } + private static Function filterSecurityCategories() { return termsAggregation -> AggregationBuilders.filter( - "filter_by_rule_types_" + termsAggregation.getName(), - termsQuery(FIELD_RULE_TYPE, - VULNERABILITY.name(), - SECURITY_HOTSPOT.name())) + "filter_by_rule_types_" + termsAggregation.getName(), + termsQuery(FIELD_RULE_TYPE, + VULNERABILITY.name(), + SECURITY_HOTSPOT.name())) .subAggregation(termsAggregation); } @@ -685,9 +712,9 @@ public class RuleIndex { private static String appendSortSuffixIfNeeded(String field) { return field + - ((field.equals(FIELD_RULE_NAME) || field.equals(FIELD_RULE_KEY)) - ? ("." + SORTABLE_ANALYZER.getSubFieldSuffix()) - : ""); + ((field.equals(FIELD_RULE_NAME) || field.equals(FIELD_RULE_KEY)) + ? ("." + SORTABLE_ANALYZER.getSubFieldSuffix()) + : ""); } private static void setPagination(SearchOptions options, SearchSourceBuilder esSearch) { @@ -731,4 +758,8 @@ public class RuleIndex { private static boolean isNotEmpty(@Nullable Collection list) { return list != null && !list.isEmpty(); } + + private static boolean isEmpty(@Nullable Collection list) { + return list == null || list.isEmpty(); + } } diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java index d9d19df1598..08cf93e12a4 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -86,6 +86,7 @@ import org.sonar.server.es.searchrequest.RequestFiltersComputer.AllFilters; import org.sonar.server.es.searchrequest.SimpleFieldTopAggregationDefinition; import org.sonar.server.es.searchrequest.SubAggregationHelper; import org.sonar.server.es.searchrequest.TopAggregationDefinition; +import org.sonar.server.es.searchrequest.TopAggregationDefinition.FilterScope; import org.sonar.server.es.searchrequest.TopAggregationDefinition.SimpleFieldFilterScope; import org.sonar.server.es.searchrequest.TopAggregationHelper; import org.sonar.server.issue.index.IssueQuery.PeriodStart; @@ -256,9 +257,8 @@ public class IssueIndex { public enum Facet { SEVERITIES(PARAM_SEVERITIES, FIELD_ISSUE_SEVERITY, STICKY, Severity.ALL.size()), - IMPACT_SOFTWARE_QUALITY(PARAM_IMPACT_SOFTWARE_QUALITIES, FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, STICKY, SoftwareQuality.values().length), - IMPACT_SEVERITY(PARAM_IMPACT_SEVERITIES, FIELD_ISSUE_IMPACT_SEVERITY, STICKY, - org.sonar.api.issue.impact.Severity.values().length), + IMPACT_SOFTWARE_QUALITY(PARAM_IMPACT_SOFTWARE_QUALITIES, FIELD_ISSUE_IMPACTS, STICKY), + IMPACT_SEVERITY(PARAM_IMPACT_SEVERITIES, FIELD_ISSUE_IMPACTS, STICKY), CLEAN_CODE_ATTRIBUTE_CATEGORY(PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES, FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY, STICKY, CleanCodeAttributeCategory.values().length), STATUSES(PARAM_STATUSES, FIELD_ISSUE_STATUS, STICKY, Issue.STATUSES.size()), // Resolutions facet returns one more element than the number of resolutions to take into account unresolved issues @@ -286,7 +286,7 @@ public class IssueIndex { CODE_VARIANTS(PARAM_CODE_VARIANTS, FIELD_ISSUE_CODE_VARIANTS, STICKY, MAX_FACET_SIZE); private final String name; - private final SimpleFieldTopAggregationDefinition topAggregation; + private final TopAggregationDefinition topAggregation; private final Integer numberOfTerms; Facet(String name, String fieldName, boolean sticky, int numberOfTerms) { @@ -309,11 +309,11 @@ public class IssueIndex { return topAggregation.getFilterScope().getFieldName(); } - public TopAggregationDefinition.FilterScope getFilterScope() { + public FilterScope getFilterScope() { return topAggregation.getFilterScope(); } - public SimpleFieldTopAggregationDefinition getTopAggregationDef() { + public TopAggregationDefinition getTopAggregationDef() { return topAggregation; } @@ -602,7 +602,8 @@ public class IssueIndex { if (query.impactSoftwareQualities().isEmpty() && query.impactSeverities().isEmpty()) { return; } - if (!query.impactSoftwareQualities().isEmpty()) { + + if (!query.impactSoftwareQualities().isEmpty() && query.impactSeverities().isEmpty()) { allFilters.addFilter( FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, IMPACT_SOFTWARE_QUALITY.getFilterScope(), @@ -610,9 +611,10 @@ public class IssueIndex { FIELD_ISSUE_IMPACTS, termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities()), ScoreMode.Avg)); + return; } - if (!query.impactSeverities().isEmpty()) { + if (!query.impactSeverities().isEmpty() && query.impactSoftwareQualities().isEmpty()) { allFilters.addFilter( FIELD_ISSUE_IMPACT_SEVERITY, IMPACT_SEVERITY.getFilterScope(), @@ -620,7 +622,15 @@ public class IssueIndex { FIELD_ISSUE_IMPACTS, termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities()), ScoreMode.Avg)); + return; } + + BoolQueryBuilder impactsFilter = boolQuery() + .filter(termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities())) + .filter(termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities())); + + allFilters.addFilter(FIELD_ISSUE_IMPACTS, new SimpleFieldFilterScope(FIELD_ISSUE_IMPACTS), + nestedQuery(FIELD_ISSUE_IMPACTS, impactsFilter, ScoreMode.Avg)); } private static void addComponentRelatedFilters(IssueQuery query, AllFilters filters) { @@ -914,7 +924,7 @@ public class IssueIndex { .map(softwareQuality -> new FiltersAggregator.KeyedFilter(softwareQuality.name(), query.impactSeverities().isEmpty() ? mainQuery.apply(softwareQuality) : mainQuery.apply(softwareQuality) - .filter(termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities())))) + .filter(termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities())))) .toArray(FiltersAggregator.KeyedFilter[]::new); AggregationBuilder aggregation = aggregationHelper.buildTopAggregation( @@ -938,7 +948,7 @@ public class IssueIndex { .map(severity -> new FiltersAggregator.KeyedFilter(severity.name(), query.impactSoftwareQualities().isEmpty() ? mainQuery.apply(severity) : mainQuery.apply(severity) - .filter(termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities())))) + .filter(termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities())))) .toArray(FiltersAggregator.KeyedFilter[]::new); AggregationBuilder aggregation = aggregationHelper.buildTopAggregation( diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java index 1171923de97..2f15ebbde63 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexFacetsTest.java @@ -715,6 +715,11 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon { entry("RELIABILITY", 0L), entry("SECURITY", 0L)); + assertThatFacetHasOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())), "impactSoftwareQualities", + entry("MAINTAINABILITY", 3L), + entry("RELIABILITY", 2L), + entry("SECURITY", 0L)); + assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(Severity.MEDIUM.name())), "impactSoftwareQualities", entry("MAINTAINABILITY", 0L), entry("RELIABILITY", 1L), @@ -733,6 +738,33 @@ public class IssueIndexFacetsTest extends IssueIndexTestCommon { entry("SECURITY", 0L)); } + @Test + public void search_whenFilteredOnSeverityAndSoftwareQuality_shouldReturnImpactFacets() { + ComponentDto project = newPrivateProjectDto(); + ComponentDto file = newFileDto(project); + + indexIssues( + newDoc("I1", project.uuid(), file).setImpacts(Map.of( + MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW, + RELIABILITY, org.sonar.api.issue.impact.Severity.LOW))); + + assertThatFacetHasOnly(IssueQuery.builder() + .impactSoftwareQualities(Set.of(MAINTAINABILITY.name())) + .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.LOW.name())), + "impactSoftwareQualities", + entry("MAINTAINABILITY", 1L), + entry("RELIABILITY", 1L), + entry("SECURITY", 0L)); + + assertThatFacetHasOnly(IssueQuery.builder() + .impactSoftwareQualities(Set.of(MAINTAINABILITY.name())) + .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.LOW.name())), + "impactSeverities", + entry("HIGH", 0L), + entry("MEDIUM", 0L), + entry("LOW", 1L)); + } + @Test public void search_shouldReturnImpactSeverityFacet() { ComponentDto project = newPrivateProjectDto(); -- 2.39.5