* SONAR-11886 Ignore Hotspots in Issues facets / filters * SONAR-11886 Do not return severity on Security Hotspotstags/7.8
@@ -53,11 +53,11 @@ import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuil | |||
import org.elasticsearch.search.aggregations.bucket.filter.InternalFilter; | |||
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval; | |||
import org.elasticsearch.search.aggregations.bucket.histogram.ExtendedBounds; | |||
import org.elasticsearch.search.aggregations.bucket.terms.IncludeExclude; | |||
import org.elasticsearch.search.aggregations.bucket.terms.InternalTerms; | |||
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; | |||
import org.elasticsearch.search.aggregations.bucket.terms.Terms; | |||
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; | |||
import org.elasticsearch.search.aggregations.bucket.terms.IncludeExclude; | |||
import org.elasticsearch.search.aggregations.metrics.max.InternalMax; | |||
import org.elasticsearch.search.aggregations.metrics.min.Min; | |||
import org.elasticsearch.search.aggregations.metrics.sum.SumAggregationBuilder; | |||
@@ -95,6 +95,8 @@ import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; | |||
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.sonar.api.rules.RuleType.SECURITY_HOTSPOT; | |||
import static org.sonar.api.rules.RuleType.VULNERABILITY; | |||
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; | |||
import static org.sonar.server.es.BaseDoc.epochMillisToEpochSeconds; | |||
import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars; | |||
@@ -356,20 +358,29 @@ public class IssueIndex { | |||
filters.put(FIELD_ISSUE_RULE_ID, createTermsFilter( | |||
FIELD_ISSUE_RULE_ID, | |||
query.rules().stream().map(RuleDefinitionDto::getId).collect(Collectors.toList()))); | |||
filters.put(FIELD_ISSUE_SEVERITY, createTermsFilter(FIELD_ISSUE_SEVERITY, query.severities())); | |||
filters.put(FIELD_ISSUE_STATUS, createTermsFilter(FIELD_ISSUE_STATUS, query.statuses())); | |||
filters.put(FIELD_ISSUE_ORGANIZATION_UUID, createTermFilter(FIELD_ISSUE_ORGANIZATION_UUID, query.organizationUuid())); | |||
filters.put(FIELD_ISSUE_OWASP_TOP_10, createTermsFilter(FIELD_ISSUE_OWASP_TOP_10, query.owaspTop10())); | |||
filters.put(FIELD_ISSUE_SANS_TOP_25, createTermsFilter(FIELD_ISSUE_SANS_TOP_25, query.sansTop25())); | |||
filters.put(FIELD_ISSUE_CWE, createTermsFilter(FIELD_ISSUE_CWE, query.cwe())); | |||
addSeverityFilter(query, filters); | |||
addComponentRelatedFilters(query, filters); | |||
addDatesFilter(filters, query); | |||
addCreatedAfterByProjectsFilter(filters, query); | |||
return filters; | |||
} | |||
private static void addSeverityFilter(IssueQuery query, Map<String, QueryBuilder> filters) { | |||
QueryBuilder severityFieldFilter = createTermsFilter(FIELD_ISSUE_SEVERITY, query.severities()); | |||
if (severityFieldFilter != null) { | |||
filters.put(FIELD_ISSUE_SEVERITY, boolQuery() | |||
.must(severityFieldFilter) | |||
// Ignore severity of Security HotSpots | |||
.mustNot(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name()))); | |||
} | |||
} | |||
private static void addComponentRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) { | |||
addCommonComponentRelatedFilters(query, filters); | |||
if (query.viewUuids().isEmpty()) { | |||
@@ -452,6 +463,19 @@ public class IssueIndex { | |||
return FACET_MODE_EFFORT.equals(query.facetMode()); | |||
} | |||
private static AggregationBuilder createSeverityFacet(IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder queryBuilder) { | |||
String fieldName = SEVERITIES.getFieldName(); | |||
String facetName = SEVERITIES.getName(); | |||
StickyFacetBuilder stickyFacetBuilder = newStickyFacetBuilder(query, filters, queryBuilder); | |||
BoolQueryBuilder facetFilter = stickyFacetBuilder.getStickyFacetFilter(fieldName) | |||
// Ignore severity of Security HotSpots | |||
.mustNot(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name())); | |||
FilterAggregationBuilder facetTopAggregation = stickyFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, SEVERITIES.getSize()); | |||
return AggregationBuilders | |||
.global(facetName) | |||
.subAggregation(facetTopAggregation); | |||
} | |||
private static AggregationBuilder createAssigneesFacet(IssueQuery query, Map<String, QueryBuilder> filters, QueryBuilder queryBuilder) { | |||
String fieldName = ASSIGNEES.getFieldName(); | |||
String facetName = ASSIGNEES.getName(); | |||
@@ -460,11 +484,11 @@ public class IssueIndex { | |||
Map<String, QueryBuilder> assigneeFilters = Maps.newHashMap(filters); | |||
assigneeFilters.remove(IS_ASSIGNED_FILTER); | |||
assigneeFilters.remove(fieldName); | |||
StickyFacetBuilder assigneeFacetBuilder = newStickyFacetBuilder(query, assigneeFilters, queryBuilder); | |||
BoolQueryBuilder facetFilter = assigneeFacetBuilder.getStickyFacetFilter(fieldName); | |||
FilterAggregationBuilder facetTopAggregation = assigneeFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, ASSIGNEES.getSize()); | |||
StickyFacetBuilder stickyFacetBuilder = newStickyFacetBuilder(query, assigneeFilters, queryBuilder); | |||
BoolQueryBuilder facetFilter = stickyFacetBuilder.getStickyFacetFilter(fieldName); | |||
FilterAggregationBuilder facetTopAggregation = stickyFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, ASSIGNEES.getSize()); | |||
if (!query.assignees().isEmpty()) { | |||
facetTopAggregation = assigneeFacetBuilder.addSelectedItemsToFacet(fieldName, facetName, facetTopAggregation, t -> t, query.assignees().toArray()); | |||
facetTopAggregation = stickyFacetBuilder.addSelectedItemsToFacet(fieldName, facetName, facetTopAggregation, t -> t, query.assignees().toArray()); | |||
} | |||
// Add missing facet for unassigned issues | |||
@@ -486,10 +510,10 @@ public class IssueIndex { | |||
Map<String, QueryBuilder> resolutionFilters = Maps.newHashMap(filters); | |||
resolutionFilters.remove("__isResolved"); | |||
resolutionFilters.remove(fieldName); | |||
StickyFacetBuilder assigneeFacetBuilder = newStickyFacetBuilder(query, resolutionFilters, esQuery); | |||
BoolQueryBuilder facetFilter = assigneeFacetBuilder.getStickyFacetFilter(fieldName); | |||
FilterAggregationBuilder facetTopAggregation = assigneeFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, RESOLUTIONS.getSize()); | |||
facetTopAggregation = assigneeFacetBuilder.addSelectedItemsToFacet(fieldName, facetName, facetTopAggregation, t -> t); | |||
StickyFacetBuilder stickyFacetBuilder = newStickyFacetBuilder(query, resolutionFilters, esQuery); | |||
BoolQueryBuilder facetFilter = stickyFacetBuilder.getStickyFacetFilter(fieldName); | |||
FilterAggregationBuilder facetTopAggregation = stickyFacetBuilder.buildTopFacetAggregation(fieldName, facetName, facetFilter, RESOLUTIONS.getSize()); | |||
facetTopAggregation = stickyFacetBuilder.addSelectedItemsToFacet(fieldName, facetName, facetTopAggregation, t -> t); | |||
// Add missing facet for unresolved issues | |||
facetTopAggregation.subAggregation( | |||
@@ -570,7 +594,6 @@ public class IssueIndex { | |||
private void configureStickyFacets(IssueQuery query, SearchOptions options, Map<String, QueryBuilder> filters, QueryBuilder esQuery, SearchRequestBuilder esSearch) { | |||
if (!options.getFacets().isEmpty()) { | |||
StickyFacetBuilder stickyFacetBuilder = newStickyFacetBuilder(query, filters, esQuery); | |||
addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch, SEVERITIES); | |||
addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch, STATUSES); | |||
addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch, PROJECT_UUIDS, query.projectUuids().toArray()); | |||
addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch, MODULE_UUIDS, query.moduleUuids().toArray()); | |||
@@ -585,6 +608,9 @@ public class IssueIndex { | |||
addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch, OWASP_TOP_10, query.owaspTop10().toArray()); | |||
addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch, SANS_TOP_25, query.sansTop25().toArray()); | |||
addSimpleStickyFacetIfNeeded(options, stickyFacetBuilder, esSearch, CWE, query.cwe().toArray()); | |||
if (options.getFacets().contains(PARAM_SEVERITIES)) { | |||
esSearch.addAggregation(createSeverityFacet(query, filters, esQuery)); | |||
} | |||
if (options.getFacets().contains(PARAM_RESOLUTIONS)) { | |||
esSearch.addAggregation(createResolutionFacet(query, filters, esQuery)); | |||
} | |||
@@ -756,7 +782,7 @@ public class IssueIndex { | |||
boolQuery() | |||
.mustNot(existsQuery(FIELD_ISSUE_RESOLUTION)) | |||
.filter(termQuery(FIELD_ISSUE_ASSIGNEE_UUID, assigneeUuid)) | |||
.mustNot(termQuery(FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name()))) | |||
.mustNot(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name()))) | |||
.setSize(0); | |||
IntStream.range(0, projectUuids.size()).forEach(i -> { | |||
String projectUuid = projectUuids.get(i); | |||
@@ -890,25 +916,25 @@ public class IssueIndex { | |||
return categoryAggs | |||
.subAggregation( | |||
AggregationBuilders.filter("vulnerabilities", boolQuery() | |||
.filter(termQuery(FIELD_ISSUE_TYPE, RuleType.VULNERABILITY.name())) | |||
.filter(termQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name())) | |||
.mustNot(existsQuery(FIELD_ISSUE_RESOLUTION))) | |||
.subAggregation( | |||
AggregationBuilders.terms("severity").field(FIELD_ISSUE_SEVERITY) | |||
.subAggregation( | |||
AggregationBuilders.count(COUNT).field(FIELD_ISSUE_KEY)))) | |||
.subAggregation(AggregationBuilders.filter("openSecurityHotspots", boolQuery() | |||
.filter(termQuery(FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name())) | |||
.filter(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name())) | |||
.mustNot(existsQuery(FIELD_ISSUE_RESOLUTION))) | |||
.subAggregation( | |||
AggregationBuilders.count(COUNT).field(FIELD_ISSUE_KEY))) | |||
.subAggregation(AggregationBuilders.filter("toReviewSecurityHotspots", boolQuery() | |||
.filter(termQuery(FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name())) | |||
.filter(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name())) | |||
.filter(termQuery(FIELD_ISSUE_STATUS, Issue.STATUS_RESOLVED)) | |||
.filter(termQuery(FIELD_ISSUE_RESOLUTION, Issue.RESOLUTION_FIXED))) | |||
.subAggregation( | |||
AggregationBuilders.count(COUNT).field(FIELD_ISSUE_KEY))) | |||
.subAggregation(AggregationBuilders.filter("wontFixSecurityHotspots", boolQuery() | |||
.filter(termQuery(FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name())) | |||
.filter(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name())) | |||
.filter(termQuery(FIELD_ISSUE_STATUS, Issue.STATUS_RESOLVED)) | |||
.filter(termQuery(FIELD_ISSUE_RESOLUTION, Issue.RESOLUTION_WONT_FIX))) | |||
.subAggregation( | |||
@@ -931,7 +957,7 @@ public class IssueIndex { | |||
return client.prepareSearch(TYPE_ISSUE.getMainType()) | |||
.setQuery( | |||
componentFilter | |||
.filter(termsQuery(FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name(), RuleType.VULNERABILITY.name())) | |||
.filter(termsQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name(), VULNERABILITY.name())) | |||
.mustNot(termQuery(FIELD_ISSUE_STATUS, Issue.STATUS_CLOSED))) | |||
.setSize(0); | |||
} |
@@ -29,6 +29,7 @@ import java.util.Set; | |||
import org.sonar.api.resources.Language; | |||
import org.sonar.api.resources.Languages; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rules.RuleType; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.api.utils.Duration; | |||
import org.sonar.api.utils.Durations; | |||
@@ -162,7 +163,7 @@ public class SearchResponseFormat { | |||
private void formatIssue(Issue.Builder issueBuilder, IssueDto dto, SearchResponseData data) { | |||
issueBuilder.setKey(dto.getKey()); | |||
ofNullable(dto.getType()).map(Common.RuleType::forNumber).ifPresent(issueBuilder::setType); | |||
issueBuilder.setType(Common.RuleType.forNumber(dto.getType())); | |||
ComponentDto component = data.getComponentByUuid(dto.getComponentUuid()); | |||
issueBuilder.setOrganization(data.getOrganizationKey(component.getOrganizationUuid())); | |||
@@ -182,7 +183,9 @@ public class SearchResponseFormat { | |||
issueBuilder.setExternalRuleEngine(engineNameFrom(dto.getRuleKey())); | |||
} | |||
issueBuilder.setFromHotspot(dto.isFromHotspot()); | |||
issueBuilder.setSeverity(Common.Severity.valueOf(dto.getSeverity())); | |||
if (dto.getType() != RuleType.SECURITY_HOTSPOT.getDbConstant()) { | |||
issueBuilder.setSeverity(Common.Severity.valueOf(dto.getSeverity())); | |||
} | |||
ofNullable(data.getUserByUuid(dto.getAssigneeUuid())).ifPresent(assignee -> issueBuilder.setAssignee(assignee.getLogin())); | |||
ofNullable(emptyToNull(dto.getResolution())).ifPresent(issueBuilder::setResolution); | |||
issueBuilder.setStatus(dto.getStatus()); |
@@ -23,10 +23,8 @@ import com.google.common.collect.ImmutableMap; | |||
import java.util.Arrays; | |||
import java.util.Date; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.stream.Collectors; | |||
import org.assertj.core.api.Fail; | |||
import org.elasticsearch.action.search.SearchResponse; | |||
import org.elasticsearch.search.SearchHit; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
@@ -40,7 +38,6 @@ import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
import org.sonar.server.es.EsTester; | |||
import org.sonar.server.es.Facets; | |||
import org.sonar.server.es.SearchOptions; | |||
import org.sonar.server.permission.index.IndexPermissions; | |||
import org.sonar.server.permission.index.PermissionIndexerTester; | |||
@@ -56,7 +53,6 @@ import static java.util.Collections.singletonList; | |||
import static java.util.TimeZone.getTimeZone; | |||
import static java.util.stream.Collectors.toList; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.entry; | |||
import static org.junit.rules.ExpectedException.none; | |||
import static org.sonar.api.resources.Qualifiers.APP; | |||
import static org.sonar.api.resources.Qualifiers.PROJECT; | |||
@@ -471,19 +467,6 @@ public class IssueIndexFiltersTest { | |||
assertThatSearchReturnsEmpty(IssueQuery.builder().severities(singletonList(Severity.BLOCKER))); | |||
} | |||
@Test | |||
public void facets_on_severities() { | |||
ComponentDto project = newPrivateProjectDto(newOrganizationDto()); | |||
ComponentDto file = newFileDto(project, null); | |||
indexIssues( | |||
newDoc("I1", file).setSeverity(Severity.INFO), | |||
newDoc("I2", file).setSeverity(Severity.INFO), | |||
newDoc("I3", file).setSeverity(Severity.MAJOR)); | |||
assertThatFacetHasOnly(IssueQuery.builder(), "severities", entry("INFO", 2L), entry("MAJOR", 1L)); | |||
} | |||
@Test | |||
public void filter_by_statuses() { | |||
ComponentDto project = newPrivateProjectDto(newOrganizationDto()); | |||
@@ -789,12 +772,4 @@ public class IssueIndexFiltersTest { | |||
List<String> keys = searchAndReturnKeys(query); | |||
assertThat(keys).isEmpty(); | |||
} | |||
@SafeVarargs | |||
private final void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) { | |||
SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet))); | |||
Facets facets = new Facets(result, system2.getDefaultTimeZone()); | |||
assertThat(facets.getNames()).containsOnly(facet, "effort"); | |||
assertThat(facets.get(facet)).containsOnly(expectedEntries); | |||
} | |||
} |
@@ -0,0 +1,166 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.server.issue.index; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.stream.Collectors; | |||
import org.elasticsearch.action.search.SearchResponse; | |||
import org.elasticsearch.search.SearchHit; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.rule.Severity; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.api.utils.internal.TestSystem2; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.server.es.EsTester; | |||
import org.sonar.server.es.Facets; | |||
import org.sonar.server.es.SearchOptions; | |||
import org.sonar.server.permission.index.IndexPermissions; | |||
import org.sonar.server.permission.index.PermissionIndexerTester; | |||
import org.sonar.server.permission.index.WebAuthorizationTypeSupport; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import static java.util.Arrays.asList; | |||
import static java.util.Arrays.stream; | |||
import static java.util.Collections.singletonList; | |||
import static java.util.TimeZone.getTimeZone; | |||
import static java.util.stream.Collectors.toList; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.entry; | |||
import static org.junit.rules.ExpectedException.none; | |||
import static org.sonar.api.resources.Qualifiers.PROJECT; | |||
import static org.sonar.api.rule.Severity.INFO; | |||
import static org.sonar.api.rule.Severity.MAJOR; | |||
import static org.sonar.api.rules.RuleType.BUG; | |||
import static org.sonar.api.rules.RuleType.CODE_SMELL; | |||
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT; | |||
import static org.sonar.api.rules.RuleType.VULNERABILITY; | |||
import static org.sonar.db.component.ComponentTesting.newFileDto; | |||
import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; | |||
import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto; | |||
import static org.sonar.server.issue.IssueDocTesting.newDoc; | |||
public class IssueIndexSecurityHotspotsTest { | |||
@Rule | |||
public EsTester es = EsTester.create(); | |||
@Rule | |||
public UserSessionRule userSessionRule = UserSessionRule.standalone(); | |||
@Rule | |||
public ExpectedException expectedException = none(); | |||
private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00")); | |||
@Rule | |||
public DbTester db = DbTester.create(system2); | |||
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient())); | |||
private PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer); | |||
private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule)); | |||
@Test | |||
public void filter_by_security_hotspots_type() { | |||
ComponentDto project = newPrivateProjectDto(newOrganizationDto()); | |||
ComponentDto file = newFileDto(project, null); | |||
indexIssues( | |||
newDoc("I1", file).setType(BUG), | |||
newDoc("I2", file).setType(CODE_SMELL), | |||
newDoc("I3", file).setType(VULNERABILITY), | |||
newDoc("I4", file).setType(VULNERABILITY), | |||
newDoc("I5", file).setType(SECURITY_HOTSPOT), | |||
newDoc("I6", file).setType(SECURITY_HOTSPOT)); | |||
assertThatSearchReturnsOnly(IssueQuery.builder().types(singletonList(SECURITY_HOTSPOT.name())), "I5", "I6"); | |||
assertThatSearchReturnsOnly(IssueQuery.builder().types(asList(SECURITY_HOTSPOT.name(), VULNERABILITY.name())), "I3", "I4", "I5", "I6"); | |||
assertThatSearchReturnsOnly(IssueQuery.builder(), "I1", "I2", "I3", "I4", "I5", "I6"); | |||
} | |||
@Test | |||
public void filter_by_severities_ignore_hotspots() { | |||
ComponentDto project = newPrivateProjectDto(newOrganizationDto()); | |||
ComponentDto file = newFileDto(project, null); | |||
indexIssues( | |||
newDoc("I1", file).setSeverity(Severity.INFO).setType(BUG), | |||
newDoc("I2", file).setSeverity(Severity.MAJOR).setType(CODE_SMELL), | |||
newDoc("I3", file).setSeverity(Severity.MAJOR).setType(VULNERABILITY), | |||
newDoc("I4", file).setSeverity(Severity.CRITICAL).setType(VULNERABILITY), | |||
// This entry should be ignored | |||
newDoc("I5", file).setSeverity(Severity.MAJOR).setType(SECURITY_HOTSPOT)); | |||
assertThatSearchReturnsOnly(IssueQuery.builder().severities(asList(Severity.INFO, Severity.MAJOR)), "I1", "I2", "I3"); | |||
assertThatSearchReturnsOnly(IssueQuery.builder().severities(asList(Severity.INFO, Severity.MAJOR)).types(singletonList(VULNERABILITY.name())), "I3"); | |||
assertThatSearchReturnsEmpty(IssueQuery.builder().severities(singletonList(Severity.MAJOR)).types(singletonList(SECURITY_HOTSPOT.name()))); | |||
} | |||
@Test | |||
public void facet_on_severities_ignore_hotspots() { | |||
ComponentDto project = newPrivateProjectDto(newOrganizationDto()); | |||
ComponentDto file = newFileDto(project, null); | |||
indexIssues( | |||
newDoc("I1", file).setSeverity(INFO).setType(BUG), | |||
newDoc("I2", file).setSeverity(INFO).setType(CODE_SMELL), | |||
newDoc("I3", file).setSeverity(INFO).setType(VULNERABILITY), | |||
newDoc("I4", file).setSeverity(MAJOR).setType(VULNERABILITY), | |||
// These 2 entries should be ignored | |||
newDoc("I5", file).setSeverity(INFO).setType(SECURITY_HOTSPOT), | |||
newDoc("I6", file).setSeverity(MAJOR).setType(SECURITY_HOTSPOT)); | |||
assertThatFacetHasOnly(IssueQuery.builder(), "severities", entry("INFO", 3L), entry("MAJOR", 1L)); | |||
assertThatFacetHasOnly(IssueQuery.builder().types(singletonList(VULNERABILITY.name())), "severities", entry("INFO", 1L), entry("MAJOR", 1L)); | |||
assertThatFacetHasOnly(IssueQuery.builder().types(asList(BUG.name(), CODE_SMELL.name(), VULNERABILITY.name())), "severities", entry("INFO", 3L), entry("MAJOR", 1L)); | |||
assertThatFacetHasOnly(IssueQuery.builder().types(singletonList(SECURITY_HOTSPOT.name())), "severities"); | |||
} | |||
private void indexIssues(IssueDoc... issues) { | |||
issueIndexer.index(asList(issues).iterator()); | |||
authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList())); | |||
} | |||
@SafeVarargs | |||
private final void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) { | |||
SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet))); | |||
Facets facets = new Facets(result, system2.getDefaultTimeZone()); | |||
assertThat(facets.getNames()).containsOnly(facet, "effort"); | |||
assertThat(facets.get(facet)).containsOnly(expectedEntries); | |||
} | |||
private void assertThatSearchReturnsOnly(IssueQuery.Builder query, String... expectedIssueKeys) { | |||
List<String> keys = searchAndReturnKeys(query); | |||
assertThat(keys).containsExactlyInAnyOrder(expectedIssueKeys); | |||
} | |||
private void assertThatSearchReturnsEmpty(IssueQuery.Builder query) { | |||
List<String> keys = searchAndReturnKeys(query); | |||
assertThat(keys).isEmpty(); | |||
} | |||
private List<String> searchAndReturnKeys(IssueQuery.Builder query) { | |||
return Arrays.stream(underTest.search(query.build(), new SearchOptions()).getHits().getHits()) | |||
.map(SearchHit::getId) | |||
.collect(Collectors.toList()); | |||
} | |||
} |
@@ -48,7 +48,6 @@ import org.sonar.server.permission.index.PermissionIndexerTester; | |||
import org.sonar.server.permission.index.WebAuthorizationTypeSupport; | |||
import org.sonar.server.rule.index.RuleIndexer; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.view.index.ViewIndexer; | |||
import static com.google.common.collect.ImmutableSortedSet.of; | |||
import static java.util.Arrays.asList; | |||
@@ -86,7 +85,6 @@ public class IssueIndexTest { | |||
public DbTester db = DbTester.create(system2); | |||
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient())); | |||
private ViewIndexer viewIndexer = new ViewIndexer(db.getDbClient(), es.client()); | |||
private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient()); | |||
private PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer); | |||
@@ -720,6 +720,52 @@ public class SearchActionTest { | |||
.containsExactlyInAnyOrder(Common.RuleType.BUG, Common.RuleType.SECURITY_HOTSPOT); | |||
} | |||
@Test | |||
public void security_hotspot_are_ignored_when_filtering_by_severities() { | |||
ComponentDto project = db.components().insertPublicProject(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
db.issues().insert(rule, project, file, i -> i.setType(RuleType.BUG).setSeverity(Severity.MAJOR.name())); | |||
db.issues().insert(rule, project, file, i -> i.setType(RuleType.VULNERABILITY).setSeverity(Severity.MAJOR.name())); | |||
db.issues().insert(rule, project, file, i -> i.setType(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR.name())); | |||
db.issues().insert(rule, project, file, i -> i.setType(RuleType.SECURITY_HOTSPOT).setSeverity(Severity.MAJOR.name())); | |||
indexPermissions(); | |||
indexIssues(); | |||
SearchWsResponse result = ws.newRequest() | |||
.setParam("severities", Severity.MAJOR.name()) | |||
.setParam(FACETS, "severities") | |||
.executeProtobuf(SearchWsResponse.class); | |||
assertThat(result.getIssuesList()) | |||
.extracting(Issue::getType) | |||
.containsExactlyInAnyOrder(Common.RuleType.BUG, Common.RuleType.VULNERABILITY, Common.RuleType.CODE_SMELL); | |||
assertThat(result.getFacets().getFacets(0).getValuesList()) | |||
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount) | |||
.containsExactlyInAnyOrder(tuple("MAJOR", 3L), tuple("INFO", 0L), tuple("MINOR", 0L), tuple("CRITICAL", 0L), tuple("BLOCKER", 0L)); | |||
} | |||
@Test | |||
public void do_not_return_severity_on_security_hotspots() { | |||
ComponentDto project = db.components().insertPublicProject(); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||
RuleDefinitionDto rule = db.rules().insert(); | |||
db.issues().insert(rule, project, file, i -> i.setType(RuleType.BUG).setSeverity(Severity.MAJOR.name())); | |||
db.issues().insert(rule, project, file, i -> i.setType(RuleType.SECURITY_HOTSPOT).setSeverity(Severity.MAJOR.name())); | |||
indexPermissions(); | |||
indexIssues(); | |||
SearchWsResponse result = ws.newRequest() | |||
.setParam("types", String.format("%s,%s", RuleType.BUG, RuleType.SECURITY_HOTSPOT)) | |||
.executeProtobuf(SearchWsResponse.class); | |||
assertThat(result.getIssuesList()) | |||
.extracting(Issue::getType, Issue::hasSeverity) | |||
.containsExactlyInAnyOrder( | |||
tuple(Common.RuleType.BUG, true), | |||
tuple(Common.RuleType.SECURITY_HOTSPOT, false)); | |||
} | |||
@Test | |||
public void return_total_effort() { | |||
UserDto john = db.users().insertUser(); |