]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11886 Ignore Hotspots in Issues facets / filters
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 4 Apr 2019 14:41:19 +0000 (16:41 +0200)
committerSonarTech <sonartech@sonarsource.com>
Tue, 23 Apr 2019 18:21:09 +0000 (20:21 +0200)
* SONAR-11886 Ignore Hotspots in Issues facets / filters
* SONAR-11886 Do not return severity on Security Hotspots

server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexFiltersTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityHotspotsTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java

index 28b2bacd4d305a752638c6efc61d4d017856bfa8..e4dbb57dcd6ecea984123a0e6e77c97cec41c5b4 100644 (file)
@@ -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);
   }
index 9ea6aaf92d1930fdf3ec9d26c4605c5d8065474b..9a92a1db6fb2f8fed08d7572a12fc9a673b06f2c 100644 (file)
@@ -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());
index 20ba5cb18670b93a6c5a722ad2d8e18265555716..94e91c8067697ab84f5bc1c3135146b3b7e4c07e 100644 (file)
@@ -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);
-  }
 }
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 (file)
index 0000000..426243c
--- /dev/null
@@ -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());
+  }
+
+}
index c390ad4e31bca1d245174b61bb262c709acf06a7..336d049ba0516851ecdd3bb69d373075fcbd14c1 100644 (file)
@@ -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);
 
index 1f44c1786777cea0de7630dc314de1ed2cb99ca5..ee64596c9da0c50a0383e40be26691cf5998b2ea 100644 (file)
@@ -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();