diff options
author | lukasz-jarocki-sonarsource <lukasz.jarocki@sonarsource.com> | 2023-08-21 16:37:34 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-08-22 20:03:05 +0000 |
commit | d4c3bbb1e8303d87260fc046841a84483711b7d3 (patch) | |
tree | 06d5d89461591281193c77f6496da449de25691a /server/sonar-server-common | |
parent | 04fc6db186342232be36c9f1b682ed218e210f9f (diff) | |
download | sonarqube-d4c3bbb1e8303d87260fc046841a84483711b7d3.tar.gz sonarqube-d4c3bbb1e8303d87260fc046841a84483711b7d3.zip |
SONAR-20198 Added rules data to elasticsearch indexes
Diffstat (limited to 'server/sonar-server-common')
8 files changed, 341 insertions, 17 deletions
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexDefinitionIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexDefinitionIT.java index b90fe9a2f77..0a9d03fe16f 100644 --- a/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexDefinitionIT.java +++ b/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexDefinitionIT.java @@ -19,9 +19,9 @@ */ package org.sonar.server.rule.index; -import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.util.List; +import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.lucene.search.TotalHits; import org.elasticsearch.client.RequestOptions; @@ -93,7 +93,7 @@ public class RuleIndexDefinitionIT { "quick", "brown", "fox", "jump", "over", "lazi", "dog"); // the following method fails if PUT fails - tester.putDocuments(TYPE_RULE, new RuleDoc(ImmutableMap.of( + tester.putDocuments(TYPE_RULE, new RuleDoc(Map.of( FIELD_RULE_UUID, "123", FIELD_RULE_HTML_DESCRIPTION, longText, FIELD_RULE_REPOSITORY, "java", 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 6a391e43abb..5292405158e 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 @@ -30,12 +30,16 @@ import java.util.function.Consumer; import org.junit.Rule; import org.junit.Test; import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.RuleStatus; +import org.sonar.api.rules.CleanCodeAttribute; import org.sonar.api.utils.System2; import org.sonar.core.util.UuidFactory; import org.sonar.core.util.UuidFactoryFast; import org.sonar.db.DbTester; +import org.sonar.db.issue.ImpactDto; import org.sonar.db.qualityprofile.QProfileDto; import org.sonar.db.rule.RuleDto; import org.sonar.server.es.EsTester; @@ -66,7 +70,9 @@ import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT; import static org.sonar.api.rules.RuleType.VULNERABILITY; import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection; import static org.sonar.db.rule.RuleTesting.newRule; +import static org.sonar.db.rule.RuleTesting.setCleanCodeAttribute; import static org.sonar.db.rule.RuleTesting.setCreatedAt; +import static org.sonar.db.rule.RuleTesting.setImpacts; import static org.sonar.db.rule.RuleTesting.setIsExternal; import static org.sonar.db.rule.RuleTesting.setIsTemplate; import static org.sonar.db.rule.RuleTesting.setLanguage; @@ -781,6 +787,143 @@ public class RuleIndexIT { } @Test + public void search_by_clean_code_attribute() { + RuleDto ruleDto = createRule(setRepositoryKey("php"), setCleanCodeAttribute(CleanCodeAttribute.FOCUSED)); + index(); + + RuleQuery query = new RuleQuery(); + query.setCleanCodeAttributesCategory(CleanCodeAttribute.LOGICAL.getAttributeCategory().name()); + SearchIdResult result1 = underTest.search(query, new SearchOptions()); + assertThat(result1.getUuids()).isEmpty(); + + + query = new RuleQuery(); + query.setCleanCodeAttributesCategory(CleanCodeAttribute.FOCUSED.getAttributeCategory().name()); + + SearchIdResult result2 = underTest.search(query, new SearchOptions()); + + assertThat(result2.getUuids()).containsOnly(ruleDto.getUuid()); + } + + @Test + public void search_by_software_quality() { + ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH).setUuid("uuid"); + RuleDto phpRule = createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto))); + index(); + + RuleQuery query = new RuleQuery(); + 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()); + } + + @Test + public void search_by_severity() { + ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH).setUuid("uuid"); + RuleDto phpRule = createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto))); + index(); + + RuleQuery query = new RuleQuery(); + 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()); + } + + @Test + public void search_should_support_clean_code_attribute_category_facet() { + createRule(setRepositoryKey("php"), setCleanCodeAttribute(CleanCodeAttribute.FOCUSED)); + createRule(setRepositoryKey("php"), setCleanCodeAttribute(CleanCodeAttribute.LOGICAL)); + index(); + + RuleQuery query = new RuleQuery(); + + SearchIdResult result2 = underTest.search(query, new SearchOptions().addFacets(singletonList("cleanCodeAttributeCategories"))); + + assertThat(result2.getFacets().getAll()).hasSize(1); + assertThat(result2.getFacets().getAll().get("cleanCodeAttributeCategories")).containsOnly(entry("ADAPTABLE", 1L), entry("INTENTIONAL", 1L)); + } + + @Test + public void search_should_support_software_quality_facet() { + ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH).setUuid("uuid"); + ImpactDto impactDto2 = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.LOW).setUuid("uuid2"); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto2))); + index(); + + RuleQuery query = new RuleQuery(); + + SearchIdResult result2 = underTest.search(query, new SearchOptions().addFacets(singletonList("impacts.softwareQuality"))); + + assertThat(result2.getFacets().getAll()).hasSize(1); + assertThat(result2.getFacets().getAll().get("impacts.softwareQuality")) + .containsOnly( + entry("SECURITY", 1L), + entry("MAINTAINABILITY", 1L), + entry("RELIABILITY", 0L)); + } + + @Test + public void search_should_support_software_quality_facet_with_filtering() { + ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH).setUuid("uuid"); + ImpactDto impactDto2 = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.LOW).setUuid("uuid2"); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto2))); + index(); + + RuleQuery query = new RuleQuery(); + + SearchIdResult result2 = underTest.search(query.setImpactSeverities(Set.of(Severity.HIGH.name())), new SearchOptions().addFacets(singletonList("impacts.softwareQuality"))); + + assertThat(result2.getFacets().getAll()).hasSize(1); + assertThat(result2.getFacets().getAll().get("impacts.softwareQuality")) + .containsOnly( + entry("SECURITY", 1L), + entry("MAINTAINABILITY", 0L), + entry("RELIABILITY", 0L)); + } + + @Test + public void search_should_support_severity_facet() { + ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH).setUuid("uuid"); + ImpactDto impactDto2 = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.LOW).setUuid("uuid2"); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto2))); + index(); + + RuleQuery query = new RuleQuery(); + + SearchIdResult result2 = underTest.search(query, new SearchOptions().addFacets(singletonList("impacts.severity"))); + + assertThat(result2.getFacets().getAll()).hasSize(1); + assertThat(result2.getFacets().getAll().get("impacts.severity")).containsOnly(entry("LOW", 1L), entry("MEDIUM", 0L), entry("HIGH", 1L)); + } + + @Test + public void search_should_support_severity_facet_with_filters() { + ImpactDto impactDto = new ImpactDto().setSoftwareQuality(SoftwareQuality.SECURITY).setSeverity(Severity.HIGH).setUuid("uuid"); + ImpactDto impactDto2 = new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.LOW).setUuid("uuid2"); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto))); + createRule(setRepositoryKey("php"), setImpacts(List.of(impactDto2))); + index(); + + RuleQuery query = new RuleQuery(); + + SearchIdResult result2 = underTest.search(query.setImpactSeverities(Set.of("LOW")), new SearchOptions().addFacets(singletonList("impacts.severity"))); + + assertThat(result2.getFacets().getAll()).hasSize(1); + assertThat(result2.getFacets().getAll().get("impacts.severity")).containsOnly(entry("LOW", 1L), entry("MEDIUM", 0L), entry("HIGH", 1L)); + } + + @Test public void global_facet_on_repositories_and_tags() { createRule(setRepositoryKey("php"), setSystemTags("sysTag"), setTags()); createRule(setRepositoryKey("php"), setSystemTags(), setTags("tag1")); 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 5b2984c4b59..798de162480 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,6 +100,15 @@ public class StickyFacetBuilder { .subAggregation(facetTopAggregation); } + public AggregationBuilder buildTopAggregationStickyFacet(String fieldName, String facetName, AggregationBuilder additionalAggregationFilter) { + BoolQueryBuilder facetFilter = getStickyFacetFilter(fieldName); + return AggregationBuilders + .global(facetName) + .subAggregation(AggregationBuilders + .filter(facetName + "_filter", facetFilter) + .subAggregation(additionalAggregationFilter)); + } + public BoolQueryBuilder getStickyFacetFilter(String... fieldNames) { BoolQueryBuilder facetFilter = boolQuery().must(query); for (Map.Entry<String, QueryBuilder> filter : filters.entrySet()) { diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java index 0b0fd257a1c..48d9c6e0f4d 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java @@ -292,8 +292,8 @@ public class IssueDoc extends BaseDoc { return this; } - public IssueDoc setImpacts(Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> softwareQualities) { - List<Map<String, String>> convertedMap = softwareQualities + public IssueDoc setImpacts(Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts) { + List<Map<String, String>> convertedMap = impacts .entrySet() .stream() .map(entry -> Map.of( diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java index 28ae666cf67..766278e9bfd 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleDoc.java @@ -24,15 +24,20 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.apache.commons.lang.builder.ReflectionToStringBuilder; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.RuleStatus; +import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleType; +import org.sonar.db.issue.ImpactDto; import org.sonar.db.rule.RuleDescriptionSectionDto; import org.sonar.db.rule.RuleDto; import org.sonar.db.rule.RuleForIndexingDto; @@ -42,6 +47,8 @@ import org.sonar.server.security.SecurityStandards; import org.sonar.server.security.SecurityStandards.SQCategory; import static java.util.stream.Collectors.joining; +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_RULE; /** @@ -290,6 +297,23 @@ public class RuleDoc extends BaseDoc { return this; } + private RuleDoc setCleanCodeAttributeCategory(String cleanCodeAttributeCategory) { + setField(RuleIndexDefinition.FIELD_RULE_CLEAN_CODE_ATTRIBUTE_CATEGORY, cleanCodeAttributeCategory); + return this; + } + + public RuleDoc setImpacts(Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts) { + List<Map<String, String>> convertedMap = impacts + .entrySet() + .stream() + .map(entry -> Map.of( + SUB_FIELD_SOFTWARE_QUALITY, entry.getKey().name(), + SUB_FIELD_SEVERITY, entry.getValue().name())) + .toList(); + setField(RuleIndexDefinition.FIELD_RULE_IMPACTS, convertedMap); + return this; + } + @Override public String toString() { return ReflectionToStringBuilder.toString(this); @@ -318,7 +342,9 @@ public class RuleDoc extends BaseDoc { .setTags(Sets.union(dto.getTags(), dto.getSystemTags())) .setUpdatedAt(dto.getUpdatedAt()) .setHtmlDescription(getConcatenatedSectionsInHtml(dto)) - .setTemplateKey(getRuleKey(dto)); + .setTemplateKey(getRuleKey(dto)) + .setCleanCodeAttributeCategory(dto.getCleanCodeAttributeCategory()) + .setImpacts(dto.getImpacts().stream().collect(Collectors.toMap(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity))); } @CheckForNull 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 a9438fcc768..7784d1607c8 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 @@ -27,7 +27,9 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Function; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; import org.apache.lucene.search.join.ScoreMode; @@ -44,12 +46,15 @@ import org.elasticsearch.join.query.JoinQueryBuilders; import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.BucketOrder; +import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregator; +import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder; import org.elasticsearch.search.aggregations.bucket.terms.IncludeExclude; import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rule.Severity; import org.sonar.api.rules.RuleType; @@ -73,7 +78,10 @@ import static org.elasticsearch.index.query.QueryBuilders.boolQuery; import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; import static org.elasticsearch.index.query.QueryBuilders.matchPhraseQuery; import static org.elasticsearch.index.query.QueryBuilders.matchQuery; +import static org.elasticsearch.index.query.QueryBuilders.nestedQuery; import static org.elasticsearch.index.query.QueryBuilders.termsQuery; +import static org.elasticsearch.search.aggregations.AggregationBuilders.filters; +import static org.elasticsearch.search.aggregations.AggregationBuilders.reverseNested; import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT; import static org.sonar.api.rules.RuleType.VULNERABILITY; import static org.sonar.server.es.EsUtils.SCROLL_TIME_IN_MINUTES; @@ -88,6 +96,10 @@ import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ 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; +import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_CLEAN_CODE_ATTRIBUTE_CATEGORY; +import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_IMPACTS; +import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_IMPACT_SEVERITY; +import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_IMPACT_SOFTWARE_QUALITY; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_CREATED_AT; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_CWE; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_HTML_DESCRIPTION; @@ -127,6 +139,7 @@ public class RuleIndex { public static final String FACET_TYPES = "types"; public static final String FACET_OLD_DEFAULT = "true"; public static final String FACET_CWE = "cwe"; + /** * @deprecated SansTop25 report is outdated, it has been completely deprecated in version 10.0 and will be removed from version 11.0 */ @@ -224,9 +237,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), @@ -244,7 +257,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); } @@ -354,9 +367,36 @@ public class RuleIndex { } } + if (query.getCleanCodeAttributesCategory() != null) { + filters.put(FIELD_RULE_CLEAN_CODE_ATTRIBUTE_CATEGORY, createTermsFilter(FIELD_RULE_CLEAN_CODE_ATTRIBUTE_CATEGORY, + Set.of(query.getCleanCodeAttributesCategory()))); + } + + addImpactFilters(query, filters); + return filters; } + private static void addImpactFilters(RuleQuery query, Map<String, QueryBuilder> allFilters) { + if (isNotEmpty(query.getImpactSoftwareQualities())) { + allFilters.put( + FIELD_RULE_IMPACT_SOFTWARE_QUALITY, + nestedQuery( + FIELD_RULE_IMPACTS, + termsQuery(FIELD_RULE_IMPACT_SOFTWARE_QUALITY, query.getImpactSoftwareQualities()), + ScoreMode.Avg)); + } + + if (isNotEmpty(query.getImpactSeverities())) { + allFilters.put( + FIELD_RULE_IMPACT_SEVERITY, + nestedQuery( + FIELD_RULE_IMPACTS, + termsQuery(FIELD_RULE_IMPACT_SEVERITY, query.getImpactSeverities()), + ScoreMode.Avg)); + } + } + private static void addSecurityStandardFilter(Map<String, QueryBuilder> filters, String key, Collection<String> values) { if (isNotEmpty(values)) { filters.put(key, @@ -424,7 +464,8 @@ public class RuleIndex { return filter; } - private static Map<String, AggregationBuilder> getFacets(RuleQuery query, SearchOptions options, QueryBuilder queryBuilder, Map<String, QueryBuilder> filters) { + private static Map<String, AggregationBuilder> getFacets(RuleQuery query, SearchOptions options, QueryBuilder queryBuilder, + Map<String, QueryBuilder> filters) { Map<String, AggregationBuilder> aggregations = new HashMap<>(); StickyFacetBuilder stickyFacetBuilder = stickyFacetBuilder(queryBuilder, filters); @@ -441,7 +482,8 @@ public class RuleIndex { return aggregations; } - private static void addDefaultFacets(RuleQuery query, SearchOptions options, Map<String, AggregationBuilder> aggregations, StickyFacetBuilder stickyFacetBuilder) { + private static void addDefaultFacets(RuleQuery query, SearchOptions options, Map<String, AggregationBuilder> aggregations, + StickyFacetBuilder stickyFacetBuilder) { if (options.getFacets().contains(FACET_LANGUAGES) || options.getFacets().contains(FACET_OLD_DEFAULT)) { Collection<String> languages = query.getLanguages(); aggregations.put(FACET_LANGUAGES, @@ -466,16 +508,71 @@ public class RuleIndex { stickyFacetBuilder.buildStickyFacet(FIELD_RULE_REPOSITORY, FACET_REPOSITORIES, MAX_FACET_SIZE, (repositories == null) ? (new String[0]) : repositories.toArray())); } + if (options.getFacets().contains(FACET_CLEAN_CODE_ATTRIBUTE_CATEGORY)) { + Collection<String> tags = query.getTags(); + aggregations.put(FACET_CLEAN_CODE_ATTRIBUTE_CATEGORY, + stickyFacetBuilder.buildStickyFacet(FIELD_RULE_CLEAN_CODE_ATTRIBUTE_CATEGORY, FACET_CLEAN_CODE_ATTRIBUTE_CATEGORY, MAX_FACET_SIZE, + (tags == null) ? (new String[0]) : tags.toArray())); + } + + addImpactSoftwareQualityFacetIfNeeded(options, query, aggregations, stickyFacetBuilder); + addImpactSeverityFacetIfNeeded(options, query, aggregations, stickyFacetBuilder); addDefaultSecurityFacets(query, options, aggregations, stickyFacetBuilder); } + private static void addImpactSoftwareQualityFacetIfNeeded(SearchOptions options, RuleQuery query, Map<String, AggregationBuilder> aggregations, + StickyFacetBuilder stickyFacetBuilder) { + if (!options.getFacets().contains(FACET_IMPACT_SOFTWARE_QUALITY)) { + return; + } + + Function<SoftwareQuality, BoolQueryBuilder> mainQuery = softwareQuality -> boolQuery() + .filter(QueryBuilders.termQuery(FIELD_RULE_IMPACT_SOFTWARE_QUALITY, softwareQuality.name())); + + 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))) + .toArray(FiltersAggregator.KeyedFilter[]::new); + + NestedAggregationBuilder nestedAggregationBuilder = AggregationBuilders.nested("nested_" + FIELD_RULE_IMPACT_SOFTWARE_QUALITY, FIELD_RULE_IMPACTS) + .subAggregation(filters(FIELD_RULE_IMPACT_SOFTWARE_QUALITY, keyedFilters)); + + AggregationBuilder aggregationBuilder = stickyFacetBuilder.buildTopAggregationStickyFacet(FIELD_RULE_IMPACT_SOFTWARE_QUALITY, FACET_IMPACT_SOFTWARE_QUALITY, nestedAggregationBuilder); + + aggregations.put(FACET_IMPACT_SOFTWARE_QUALITY, aggregationBuilder); + } + + private static void addImpactSeverityFacetIfNeeded(SearchOptions options, RuleQuery query, Map<String, AggregationBuilder> aggregations, StickyFacetBuilder stickyFacetBuilder) { + if (!options.getFacets().contains(FACET_IMPACT_SEVERITY)) { + return; + } + + Function<org.sonar.api.issue.impact.Severity, BoolQueryBuilder> mainQuery = softwareQuality -> boolQuery() + .filter(QueryBuilders.termQuery(FIELD_RULE_IMPACT_SEVERITY, softwareQuality.name())); + + 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_SEVERITY, query.getImpactSoftwareQualities())) + : mainQuery.apply(severity))) + .toArray(FiltersAggregator.KeyedFilter[]::new); + + NestedAggregationBuilder nestedAggregationBuilder = AggregationBuilders.nested("nested_" + FIELD_RULE_IMPACT_SEVERITY, FIELD_RULE_IMPACTS) + .subAggregation(filters(FIELD_RULE_IMPACT_SEVERITY, keyedFilters).subAggregation(reverseNested("reverse_nested_" + FIELD_RULE_IMPACT_SEVERITY))); + + AggregationBuilder aggregationBuilder = stickyFacetBuilder.buildTopAggregationStickyFacet(FIELD_RULE_IMPACT_SEVERITY, FACET_IMPACT_SEVERITY, nestedAggregationBuilder); + + aggregations.put(FACET_IMPACT_SEVERITY, aggregationBuilder); + } + private static Function<TermsAggregationBuilder, AggregationBuilder> 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); } @@ -629,6 +726,11 @@ public class RuleIndex { return EsUtils.termsKeys(esResponse.getAggregations().get(AGGREGATION_NAME_FOR_TAGS)); } + @CheckForNull + private static QueryBuilder createTermsFilter(String field, Collection<?> values) { + return values.isEmpty() ? null : termsQuery(field, values); + } + private static boolean isNotEmpty(@Nullable Collection<?> list) { return list != null && !list.isEmpty(); } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndexDefinition.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndexDefinition.java index d824901b263..928c656c2ff 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndexDefinition.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndexDefinition.java @@ -19,7 +19,6 @@ */ package org.sonar.server.rule.index; -import com.google.common.collect.ImmutableSet; import java.util.Set; import javax.inject.Inject; import org.sonar.api.config.Configuration; @@ -68,7 +67,7 @@ public class RuleIndexDefinition implements IndexDefinition { public static final String FIELD_RULE_SONARSOURCE_SECURITY = "sonarsourceSecurity"; public static final String FIELD_RULE_TAGS = "tags"; - public static final Set<String> SORT_FIELDS = ImmutableSet.of( + public static final Set<String> SORT_FIELDS = Set.of( FIELD_RULE_NAME, FIELD_RULE_UPDATED_AT, FIELD_RULE_CREATED_AT, @@ -81,6 +80,13 @@ public class RuleIndexDefinition implements IndexDefinition { public static final String FIELD_ACTIVE_RULE_PROFILE_UUID = "activeRule_ruleProfile"; public static final String FIELD_ACTIVE_RULE_SEVERITY = "activeRule_severity"; + public static final String FIELD_RULE_CLEAN_CODE_ATTRIBUTE_CATEGORY = "cleanCodeAttributeCategory"; + public static final String FIELD_RULE_IMPACTS = "impacts"; + public static final String SUB_FIELD_SOFTWARE_QUALITY = "softwareQuality"; + public static final String SUB_FIELD_SEVERITY = "severity"; + public static final String FIELD_RULE_IMPACT_SOFTWARE_QUALITY = FIELD_RULE_IMPACTS + "." + SUB_FIELD_SOFTWARE_QUALITY; + public static final String FIELD_RULE_IMPACT_SEVERITY = FIELD_RULE_IMPACTS + "." + SUB_FIELD_SEVERITY; + private final Configuration config; private final boolean enableSource; @@ -149,6 +155,12 @@ public class RuleIndexDefinition implements IndexDefinition { ruleMapping.keywordFieldBuilder(FIELD_RULE_SANS_TOP_25).disableNorms().build(); ruleMapping.keywordFieldBuilder(FIELD_RULE_SONARSOURCE_SECURITY).disableNorms().build(); + ruleMapping.keywordFieldBuilder(FIELD_RULE_CLEAN_CODE_ATTRIBUTE_CATEGORY).disableNorms().build(); + ruleMapping.nestedFieldBuilder(FIELD_RULE_IMPACTS) + .addKeywordField(SUB_FIELD_SOFTWARE_QUALITY) + .addKeywordField(SUB_FIELD_SEVERITY) + .build(); + // Active rule index.createTypeMapping(TYPE_ACTIVE_RULE) .keywordFieldBuilder(FIELD_ACTIVE_RULE_UUID).disableNorms().build() diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleQuery.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleQuery.java index 3cab7dc9bd8..0e013e24a2d 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleQuery.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleQuery.java @@ -58,6 +58,10 @@ public class RuleQuery { private Collection<String> sansTop25; private Collection<String> cwe; private Collection<String> sonarsourceSecurity; + private Collection<String> impactSeverities; + private Collection<String> impactSoftwareQualities; + private String cleanCodeAttributesCategory; + @CheckForNull public QProfileDto getQProfile() { @@ -337,4 +341,32 @@ public class RuleQuery { this.sonarsourceSecurity = sonarsourceSecurity; return this; } + + public Collection<String> getImpactSeverities() { + return impactSeverities; + } + + public RuleQuery setImpactSeverities(Collection<String> impactSeverities) { + this.impactSeverities = impactSeverities; + return this; + } + + public Collection<String> getImpactSoftwareQualities() { + return impactSoftwareQualities; + } + + public RuleQuery setImpactSoftwareQualities(Collection<String> impactSoftwareQualities) { + this.impactSoftwareQualities = impactSoftwareQualities; + return this; + } + + public String getCleanCodeAttributesCategory() { + return cleanCodeAttributesCategory; + } + + public RuleQuery setCleanCodeAttributesCategory(String cleanCodeAttributesCategory) { + this.cleanCodeAttributesCategory = cleanCodeAttributesCategory; + return this; + } + } |