List<RuleDto> 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<SecurityStandards.SQCategory, Set<String>> sqCategorySetEntry : SecurityStandards.CWES_BY_SQ_CATEGORY.entrySet()) {
rules.add(createRule(setSecurityStandards(of("cwe:" + sqCategorySetEntry.getValue().iterator().next())), r -> r.setType(SECURITY_HOTSPOT)));
}
SearchIdResult result1 = underTest.search(query, new SearchOptions());
assertThat(result1.getUuids()).isEmpty();
-
query = new RuleQuery();
query.setCleanCodeAttributesCategories(List.of(CleanCodeAttribute.FOCUSED.getAttributeCategory().name()));
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());
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());
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");
.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
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;
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;
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),
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);
}
}
private static void addImpactFilters(RuleQuery query, Map<String, QueryBuilder> 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<String, QueryBuilder> filters, String key, Collection<String> values) {
return;
}
- Function<SoftwareQuality, BoolQueryBuilder> mainQuery = softwareQuality -> boolQuery()
- .filter(QueryBuilders.termQuery(FIELD_RULE_IMPACT_SOFTWARE_QUALITY, softwareQuality.name()));
+ Function<String, BoolQueryBuilder> 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<String, BoolQueryBuilder> 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<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()));
+ Function<String, BoolQueryBuilder> 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<String, BoolQueryBuilder> 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<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);
}
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) {
private static boolean isNotEmpty(@Nullable Collection<?> list) {
return list != null && !list.isEmpty();
}
+
+ private static boolean isEmpty(@Nullable Collection<?> list) {
+ return list == null || list.isEmpty();
+ }
}
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;
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
CODE_VARIANTS(PARAM_CODE_VARIANTS, FIELD_ISSUE_CODE_VARIANTS, STICKY, MAX_FACET_SIZE);
private final String name;
- private final SimpleFieldTopAggregationDefinition topAggregation;
+ private final TopAggregationDefinition<FilterScope> topAggregation;
private final Integer numberOfTerms;
Facet(String name, String fieldName, boolean sticky, int numberOfTerms) {
return topAggregation.getFilterScope().getFieldName();
}
- public TopAggregationDefinition.FilterScope getFilterScope() {
+ public FilterScope getFilterScope() {
return topAggregation.getFilterScope();
}
- public SimpleFieldTopAggregationDefinition getTopAggregationDef() {
+ public TopAggregationDefinition<FilterScope> getTopAggregationDef() {
return topAggregation;
}
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(),
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(),
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) {
.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(
.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(
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),
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();