aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-server
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2019-04-04 16:41:19 +0200
committerSonarTech <sonartech@sonarsource.com>2019-04-23 20:21:09 +0200
commitff6994bef060d563218a94010ca8ca09741f50fb (patch)
tree4e4a0e25b7060c944e88fded58ea820d7e90cbf5 /server/sonar-server
parent5c55b4a5e6a71a53cbd8d3fc9d53f3f247e38b5f (diff)
downloadsonarqube-ff6994bef060d563218a94010ca8ca09741f50fb.tar.gz
sonarqube-ff6994bef060d563218a94010ca8ca09741f50fb.zip
SONAR-11886 Ignore Hotspots in Issues facets / filters
* SONAR-11886 Ignore Hotspots in Issues facets / filters * SONAR-11886 Do not return severity on Security Hotspots
Diffstat (limited to 'server/sonar-server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java62
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java7
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java25
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityHotspotsTest.java166
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java46
6 files changed, 261 insertions, 47 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
index 28b2bacd4d3..e4dbb57dcd6 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
@@ -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);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
index 9ea6aaf92d1..9a92a1db6fb 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
@@ -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());
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
index 20ba5cb1867..94e91c80676 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
@@ -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;
@@ -472,19 +468,6 @@ public class IssueIndexFiltersTest {
}
@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());
ComponentDto file = newFileDto(project, null);
@@ -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);
- }
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityHotspotsTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityHotspotsTest.java
new file mode 100644
index 00000000000..426243cd7d6
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityHotspotsTest.java
@@ -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());
+ }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
index c390ad4e31b..336d049ba05 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
@@ -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);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java
index 1f44c178677..ee64596c9da 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java
@@ -721,6 +721,52 @@ public class SearchActionTest {
}
@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();
userSession.logIn(john);